diff --git a/.clang-tidy b/.clang-tidy index 73ff6a07..ee77ff14 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,6 +10,7 @@ Checks: ' -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-avoid-reference-coroutine-parameters, misc-*, -misc-no-recursion, -misc-non-private-member-variables-in-classes, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1482a4f6..4ebc5e85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.8.2@sha256:8d6d0b49bef9b1f572793dee8dcc05edcbe4f44f108f075640dda284ff3e2d4e # v6.8.2 strategy: matrix: - target: [Host, Windows] + target: [Host, Host-Asynchronous, Windows] fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -65,8 +65,8 @@ jobs: max-size: 2G - uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 with: - configurePreset: "Host" - buildPreset: "Host-Debug" + configurePreset: "Host-Asynchronous" + buildPreset: "Host-Asynchronous-Debug" - run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0 @@ -81,7 +81,7 @@ jobs: matrix: os: [macos, windows, ubuntu] version: [latest] - type: [synchronous] + type: [synchronous, asynchronous] fail-fast: false steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.vscode/launch.json b/.vscode/launch.json index a043d84a..42a4b209 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,7 +46,10 @@ "args": [ "--format", "pretty", - "summary", + // "summary", + "--parallel", + "2", + "--fail-fast", "--retry", "1", "--tags", diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b23b7f1..e866ab7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") project(cucumber-cpp-runner LANGUAGES C CXX VERSION 4.0.0) # x-release-please-version +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED On) +set(CMAKE_CXX_EXTENSIONS Off) + include(ccr_test_helpers) if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) @@ -18,10 +22,12 @@ else() set(CCR_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL") endif() + 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) option(CCR_ENABLE_TIME_PROFILE "Enable compiler flags for time profiling measurements" Off) +option(CCR_ENABLE_PARALLEL_SUPPORT "Enable parallel support through libcoro" Off) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -57,10 +63,6 @@ if (CCR_ENABLE_TIME_PROFILE) endif() endif() -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED On) -set(CMAKE_CXX_EXTENSIONS Off) - set_directory_properties(PROPERTY USE_FOLDERS ON) include(FetchContent) @@ -78,6 +80,10 @@ else() find_package(cucumber_gherkin REQUIRED) find_package(fmt REQUIRED) + if(CCR_ENABLE_PARALLEL_SUPPORT) + find_package(libcoro REQUIRED) + endif() + if (CCR_BUILD_TESTS) find_package(yaml-cpp REQUIRED) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 76343f1c..e60d3ad2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -24,7 +24,8 @@ "generator": "Ninja", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", - "CCR_ENABLE_COVERAGE": "On" + "CCR_ENABLE_COVERAGE": "On", + "CCR_ENABLE_PARALLEL_SUPPORT": "On" } }, { @@ -32,6 +33,14 @@ "inherits": "defaults", "generator": "Ninja Multi-Config" }, + { + "name": "Host-Asynchronous", + "inherits": "Host", + "generator": "Ninja Multi-Config", + "cacheVariables": { + "CCR_ENABLE_PARALLEL_SUPPORT": "On" + } + }, { "name": "host-single-Debug-synchronous", "hidden": true, @@ -57,6 +66,32 @@ "displayName": "Configuration for macOS Debug and Tests, Single Config Generator, synchronous", "inherits": "host-single-Debug-synchronous" }, + { + "name": "host-single-Debug-asynchronous", + "hidden": true, + "inherits": "defaults", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CCR_ENABLE_PARALLEL_SUPPORT": "On" + } + }, + { + "name": "ubuntu-Debug-asynchronous", + "displayName": "Configuration for Linux Debug and Tests, Single Config Generator, asynchronous", + "inherits": "host-single-Debug-asynchronous" + }, + { + "name": "windows-Debug-asynchronous", + "displayName": "Configuration for Windows Debug and Tests, Single Config Generator, asynchronous", + "inherits": "host-single-Debug-asynchronous", + "toolchainFile": "${sourceDir}/cmake/Windows.MSVC.toolchain.cmake" + }, + { + "name": "macos-Debug-asynchronous", + "displayName": "Configuration for macOS Debug and Tests, Single Config Generator, asynchronous", + "inherits": "host-single-Debug-asynchronous" + }, { "name": "host-single-time-profile", "displayName": "Configuration time profiling", @@ -86,6 +121,13 @@ "CCACHE_COMPILERTYPE": "clang-cl" }, "generator": "Ninja Multi-Config" + }, + { + "name": "Windows-Asynchronous", + "inherits": "Windows", + "cacheVariables": { + "CCR_ENABLE_PARALLEL_SUPPORT": "On" + } } ], "buildPresets": [ @@ -104,6 +146,17 @@ "configurePreset": "Host", "jobs": 4 }, + { + "name": "Host-Asynchronous-Release", + "configuration": "Release", + "configurePreset": "Host-Asynchronous" + }, + { + "name": "Host-Asynchronous-Debug", + "configuration": "Debug", + "configurePreset": "Host-Asynchronous", + "jobs": 4 + }, { "name": "Host-Iwyu-Debug", "configuration": "Debug", @@ -124,6 +177,21 @@ "configuration": "Debug", "configurePreset": "macos-Debug-synchronous" }, + { + "name": "ubuntu-Debug-asynchronous", + "configuration": "Debug", + "configurePreset": "ubuntu-Debug-asynchronous" + }, + { + "name": "windows-Debug-asynchronous", + "configuration": "Debug", + "configurePreset": "windows-Debug-asynchronous" + }, + { + "name": "macos-Debug-asynchronous", + "configuration": "Debug", + "configurePreset": "macos-Debug-asynchronous" + }, { "name": "host-single-time-profile", "configuration": "Debug", @@ -150,7 +218,9 @@ "name": "defaults", "hidden": true, "output": { - "outputOnFailure": true + "outputOnFailure": true, + "verbosity": "extra", + "debug": true }, "execution": { "noTestsAction": "error", @@ -192,6 +262,24 @@ "configurePreset": "macos-Debug-synchronous", "configuration": "Debug", "inherits": "defaults" + }, + { + "name": "ubuntu-Debug-asynchronous", + "configurePreset": "ubuntu-Debug-asynchronous", + "configuration": "Debug", + "inherits": "defaults" + }, + { + "name": "windows-Debug-asynchronous", + "configurePreset": "windows-Debug-asynchronous", + "configuration": "Debug", + "inherits": "defaults" + }, + { + "name": "macos-Debug-asynchronous", + "configurePreset": "macos-Debug-asynchronous", + "configuration": "Debug", + "inherits": "defaults" } ], "workflowPresets": [ @@ -245,6 +333,57 @@ "name": "macos-Debug-synchronous" } ] + }, + { + "name": "ubuntu-Debug-asynchronous", + "steps": [ + { + "type": "configure", + "name": "ubuntu-Debug-asynchronous" + }, + { + "type": "build", + "name": "ubuntu-Debug-asynchronous" + }, + { + "type": "test", + "name": "ubuntu-Debug-asynchronous" + } + ] + }, + { + "name": "windows-Debug-asynchronous", + "steps": [ + { + "type": "configure", + "name": "windows-Debug-asynchronous" + }, + { + "type": "build", + "name": "windows-Debug-asynchronous" + }, + { + "type": "test", + "name": "windows-Debug-asynchronous" + } + ] + }, + { + "name": "macos-Debug-asynchronous", + "steps": [ + { + "type": "configure", + "name": "macos-Debug-asynchronous" + }, + { + "type": "build", + "name": "macos-Debug-asynchronous" + }, + { + "type": "test", + "name": "macos-Debug-asynchronous" + } + ] } ] } diff --git a/compatibility/BaseCompatibility.cpp b/compatibility/BaseCompatibility.cpp index aae24f9c..d1ae0223 100644 --- a/compatibility/BaseCompatibility.cpp +++ b/compatibility/BaseCompatibility.cpp @@ -236,7 +236,7 @@ namespace compatibility auto contextStorageFactory{ std::make_shared() }; auto programContext{ std::make_unique(contextStorageFactory) }; - cucumber_cpp::library::util::Broadcaster broadcaster; + cucumber_cpp::library::util::BroadcasterImpl broadcaster; BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "expected.ndjson", devkit.ndjsonFile.parent_path() / "actual.ndjson", broadcaster }; diff --git a/compatibility/ambiguous/ambiguous.cpp b/compatibility/ambiguous/ambiguous.cpp index 0a21f56b..f9eaec81 100644 --- a/compatibility/ambiguous/ambiguous.cpp +++ b/compatibility/ambiguous/ambiguous.cpp @@ -1,12 +1,12 @@ #include "cucumber_cpp/Steps.hpp" #include -STEP(R"(^a (.*?) with (.*?)$)", (const std::string& arg1, const std::string& arg2)) +STEP(R"(^a (.*?) with (.*?)$)", ([[maybe_unused]] const std::string& arg1, [[maybe_unused]] const std::string& arg2)) { // no-op } -STEP(R"(^a step with (.*?)$)", (const std::string& arg1)) +STEP(R"(^a step with (.*?)$)", ([[maybe_unused]] const std::string& arg1)) { // no-op } diff --git a/compatibility/minimal/minimal.cpp b/compatibility/minimal/minimal.cpp index c0a8a2a6..2b33833c 100644 --- a/compatibility/minimal/minimal.cpp +++ b/compatibility/minimal/minimal.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/Steps.hpp" #include -STEP(R"(I have {int} cukes in my belly)", (std::int32_t number)) +STEP(R"(I have {int} cukes in my belly)", ([[maybe_unused]] std::int32_t number)) { // no-op } diff --git a/compatibility/regular-expression/regular-expression.cpp b/compatibility/regular-expression/regular-expression.cpp index b626266d..0ac85382 100644 --- a/compatibility/regular-expression/regular-expression.cpp +++ b/compatibility/regular-expression/regular-expression.cpp @@ -2,7 +2,7 @@ #include #include -STEP(R"(^a (.*?)(?: and a (.*?))?(?: and a (.*?))?$)", (const std::optional& vegetable1, const std::optional& vegetable2, const std::optional& vegetable3)) +STEP(R"(^a (.*?)(?: and a (.*?))?(?: and a (.*?))?$)", ([[maybe_unused]] const std::optional& vegetable1, [[maybe_unused]] const std::optional& vegetable2, [[maybe_unused]] const std::optional& vegetable3)) { // no-op } diff --git a/compatibility/rules-backgrounds/rules-backgrounds.cpp b/compatibility/rules-backgrounds/rules-backgrounds.cpp index 2df02212..d9bee7df 100644 --- a/compatibility/rules-backgrounds/rules-backgrounds.cpp +++ b/compatibility/rules-backgrounds/rules-backgrounds.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/Steps.hpp" #include -GIVEN(R"(an order for {string})", (const std::string& order)) +GIVEN(R"(an order for {string})", ([[maybe_unused]] const std::string& order)) { // no-op } diff --git a/compatibility/unknown-parameter-type/unknown-parameter-type.cpp b/compatibility/unknown-parameter-type/unknown-parameter-type.cpp index 34fda945..ce6d1ada 100644 --- a/compatibility/unknown-parameter-type/unknown-parameter-type.cpp +++ b/compatibility/unknown-parameter-type/unknown-parameter-type.cpp @@ -4,7 +4,7 @@ struct Airport {}; -GIVEN(R"({airport} is closed because of a strike)", (const Airport& airport)) +GIVEN(R"({airport} is closed because of a strike)", ([[maybe_unused]] const Airport& airport)) { FAIL() << "Should not be called because airport parameter type has not been defined"; } diff --git a/cucumber_cpp/acceptance_test/features/test_optional_feature_hooks.feature b/cucumber_cpp/acceptance_test/features/test_optional_feature_hooks.feature new file mode 100644 index 00000000..e37c6823 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_optional_feature_hooks.feature @@ -0,0 +1,4 @@ +@featurehook +Feature: Test feature hook bindings + Scenario: Run feature hooks before and after + Given a given step diff --git a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp index de7fc35e..605fc040 100644 --- a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp +++ b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/Steps.hpp" #include "gmock/gmock.h" #include #include @@ -17,6 +18,16 @@ HOOK_AFTER_ALL() std::cout << "HOOK_AFTER_ALL\n"; } +HOOK_BEFORE_FEATURE("@featurehook") +{ + std::cout << "HOOK_BEFORE_FEATURE\n"; +} + +HOOK_AFTER_FEATURE("@featurehook") +{ + std::cout << "HOOK_AFTER_FEATURE\n"; +} + HOOK_BEFORE_SCENARIO("@scenariohook and @bats") { std::cout << "HOOK_BEFORE_SCENARIO\n"; diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index a6cc3119..49a1800f 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/Steps.hpp" #include "fmt/format.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index d7ba7f2f..66ebe93d 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -222,3 +222,34 @@ teardown() { assert_output --partial "| this step is used | - |" assert_output --partial "| This step is unused | UNUSED |" } + +@test "Test feature hooks enabled" { + run $acceptance_test --tags "@featurehook" --feature-hooks -- cucumber_cpp/acceptance_test/features + assert_success + assert_output --partial "HOOK_BEFORE_FEATURE" + assert_output --partial "HOOK_AFTER_FEATURE" +} + +@test "Test feature hooks disabled by default" { + run $acceptance_test --tags "@featurehook" -- cucumber_cpp/acceptance_test/features + assert_success + refute_output --partial "HOOK_BEFORE_FEATURE" + refute_output --partial "HOOK_AFTER_FEATURE" +} + +@test "Test feature hooks disabled explicitly" { + run $acceptance_test --tags "@featurehook" --no-feature-hooks -- cucumber_cpp/acceptance_test/features + assert_success + refute_output --partial "HOOK_BEFORE_FEATURE" + refute_output --partial "HOOK_AFTER_FEATURE" +} + +@test "Successful asynchronous test" { + run $acceptance_test --format summary pretty message junit --parallel 2 --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + assert_success +} + +@test "Failed asynchronous tests" { + run $acceptance_test --format summary pretty message junit --parallel 2 --tags "@smoke and @result:FAILED" -- cucumber_cpp/acceptance_test/features + assert_failure +} diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 51516fcc..50657f3e 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/Application.hpp" +#include "CLI/CLI.hpp" #include "cucumber/gherkin/demangle.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Errors.hpp" @@ -119,6 +120,10 @@ namespace cucumber_cpp::library cli.add_flag("--feature-hooks,!--no-feature-hooks", options.featureHooks, "Run Before/After Feature hooks, note these are non-standard and are not supported by messages")->default_val(options.featureHooks); cli.add_flag("--recursive,!--no-recursive", options.recursive, "Search for feature files recursively")->default_val(options.recursive); +#if defined(ENABLE_PARALLEL_SUPPORT) + cli.add_option("--parallel", options.parallel, "Number of parallel workers to run scenarios. Default 0 (no parallelism)")->default_val(options.parallel); +#endif + CLI::deprecate_option(cli.add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); cli.add_option("-t,--tags", options.tags, "Cucumber tag expression"); @@ -198,6 +203,7 @@ namespace cucumber_cpp::library .strict = options.strict, .retryTagExpression = tag_expression::Parse(fmt::to_string(fmt::join(options.retryTagFilter, " "))), .featureHooks = options.featureHooks, + .parallel = options.parallel, }, }; diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 94cabed4..6807629f 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -45,8 +46,8 @@ namespace cucumber_cpp::library enum support::RunOptions::Ordering ordering{ support::RunOptions::Ordering::defined }; - std::size_t retry{ 0 }; - std::vector retryTagFilter{}; + std::uint32_t retry{ 0 }; + std::vector retryTagFilter; bool strict{ true }; @@ -54,7 +55,9 @@ namespace cucumber_cpp::library bool recursive{ true }; - std::vector tags{}; + std::vector tags; + + std::uint32_t parallel{ 0 }; }; explicit Application(std::shared_ptr contextStorageFactory = std::make_shared(), bool removeDefaultGoogleTestListener = true); @@ -80,14 +83,12 @@ namespace cucumber_cpp::library api::Formatters formatters; - util::Broadcaster broadcaster; + util::BroadcasterImpl broadcaster; cucumber_expression::ParameterRegistry parameterRegistry{ cucumber_cpp::library::support::DefinitionRegistration::Instance().GetRegisteredParameters() }; bool removeDefaultGoogleTestListener; util::StopWatchHighResolutionClock stopwatchHighResolutionClock; util::TimestampGeneratorSystemClock timestampGeneratorSystemClock; - - bool runPassed{ false }; }; } diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index 551b447c..0aa60ec5 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -25,18 +25,18 @@ namespace cucumber_cpp::library::engine nestedTestCaseRunner.Step(step); } - void StepBase::Step(const std::string& step, const std::optional& docString) const + void StepBase::Step(const std::string& step, const std::optional& nestedDocString) const { - nestedTestCaseRunner.Step(step, util::TransformDocString(docString)); + nestedTestCaseRunner.Step(step, util::TransformDocString(nestedDocString)); } - void StepBase::Step(const std::string& step, const std::optional& dataTable) const + void StepBase::Step(const std::string& step, const std::optional& nestedDataTable) const { - nestedTestCaseRunner.Step(step, util::TransformTable(dataTable)); + nestedTestCaseRunner.Step(step, util::TransformTable(nestedDataTable)); } - void StepBase::Step(const std::string& step, const std::optional& dataTable, const std::optional& docString) const + void StepBase::Step(const std::string& step, const std::optional& nestedDataTable, const std::optional& nestedDocString) const { - nestedTestCaseRunner.Step(step, util::TransformTable(dataTable), util::TransformDocString(docString)); + nestedTestCaseRunner.Step(step, util::TransformTable(nestedDataTable), util::TransformDocString(nestedDocString)); } } diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index 63d449d1..05b9c84a 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -41,9 +41,9 @@ namespace cucumber_cpp::library::engine protected: 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; + void Step(const std::string& step, const std::optional& nestedDocString) const; + void Step(const std::string& step, const std::optional& nestedDataTable) const; + void Step(const std::string& step, const std::optional& nestedDataTable, const std::optional& nestedDocString) const; const runtime::NestedTestCaseRunner& nestedTestCaseRunner; diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index 046dc99a..7979c8b0 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -40,7 +40,7 @@ namespace cucumber_cpp::library::engine struct TestStep : testing::Test { - util::Broadcaster broadcaster; + util::BroadcasterImpl broadcaster; std::shared_ptr contextStorageFactory{ std::make_shared() }; Context context{ contextStorageFactory }; util::StepOrHookStarted stepOrHookStarted; diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 121ee374..90fce5c6 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -181,7 +181,7 @@ namespace cucumber_cpp::library::formatter const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); auto isBeforeHook = true; - for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) + for (const auto& [testStepFinished, testStep] : testStepFinishedAndTestStep) { if (testStep->hook_id.has_value()) HandleHookStep(stream, query, *testStepFinished, *testStep, scenarioIndent, maxContentLength, isBeforeHook, useStatusIcon, theme); @@ -201,8 +201,6 @@ namespace cucumber_cpp::library::formatter 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); diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp index 51de86ba..878f4144 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.cpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -151,7 +151,7 @@ namespace cucumber_cpp::library::formatter const auto& testCase = query.FindTestCaseBy(testCaseStarted); const auto testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); - for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) + for (const auto& [testStepFinished, testStep] : testStepFinishedAndTestStep) AddUsageMatchToMapping(query, *testStepFinished, *testStep, testCase, mapping); } } diff --git a/cucumber_cpp/library/formatter/helper/Theme.cpp b/cucumber_cpp/library/formatter/helper/Theme.cpp index 0021d621..f01c308c 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.cpp +++ b/cucumber_cpp/library/formatter/helper/Theme.cpp @@ -3,7 +3,6 @@ #include "fmt/color.h" #include #include -#include #include #include #include @@ -42,11 +41,6 @@ namespace cucumber_cpp::library::formatter::helper { cucumber::messages::test_step_result_status::UNKNOWN, "?" }, }; - std::optional GetColorStyle(std::optional def) - { - return def; - } - const std::regex ansiEscape{ "\033\\[[^m]+m" }; } diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index 586ac8e2..73d25518 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -112,7 +112,7 @@ namespace cucumber_cpp::library::query }; struct Query - : util::Broadcaster + : util::BroadcasterImpl , util::Listener { explicit Query(util::Broadcaster& broadcaster); diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index ce295e84..1787a7ba 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -27,6 +27,22 @@ target_link_libraries(cucumber_cpp.library.runtime PUBLIC fmt::fmt ) +if (CCR_ENABLE_PARALLEL_SUPPORT) + target_sources(cucumber_cpp.library.runtime PRIVATE + ParallelRuntimeAdapter.cpp + ParallelRuntimeAdapter.hpp + ) + + target_link_libraries(cucumber_cpp.library.runtime PUBLIC + libcoro + ) + + target_compile_definitions(cucumber_cpp.library.runtime PUBLIC + ENABLE_PARALLEL_SUPPORT + ) +endif() + + if (CCR_BUILD_TESTS) add_subdirectory(test) # add_subdirectory(test_helper) diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp index 04b88d83..0af71e53 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.cpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -9,19 +9,39 @@ #include #include #include +#include + +#if defined(ENABLE_PARALLEL_SUPPORT) +#include "cucumber_cpp/library/runtime/ParallelRuntimeAdapter.hpp" +#endif 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) + namespace { - return std::make_unique( - testRunStartedId, - broadcaster, - idGenerator, - sourcedPickles, - options, - supportCodeLibrary, - 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) + { +#if defined(ENABLE_PARALLEL_SUPPORT) + if (options.parallel > 0) + return std::make_unique( + std::move(testRunStartedId), + broadcaster, + std::move(idGenerator), + sourcedPickles, + options, + supportCodeLibrary, + programContext); + else +#endif + return std::make_unique( + std::move(testRunStartedId), + broadcaster, + std::move(idGenerator), + sourcedPickles, + options, + supportCodeLibrary, + 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) diff --git a/cucumber_cpp/library/runtime/MakeRuntime.hpp b/cucumber_cpp/library/runtime/MakeRuntime.hpp index 3b3f5953..ab034f92 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.hpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.hpp @@ -8,12 +8,9 @@ #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, const std::list& sourcedPickles, support::SupportCodeLibrary& supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); } diff --git a/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.cpp new file mode 100644 index 00000000..8388357e --- /dev/null +++ b/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.cpp @@ -0,0 +1,170 @@ +#include "cucumber_cpp/library/runtime/ParallelRuntimeAdapter.hpp" +#include "coro/task.hpp" +#include "coro/thread_pool.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/Context.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/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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + namespace + { + bool IsFailing(cucumber::messages::test_step_result_status status, bool dryRun) + { + if (dryRun) + return false; + + return status != cucumber::messages::test_step_result_status::PASSED; + } + + coro::task RunTestCase(std::unique_ptr& tp, coro::latch& tasksLatch, runtime::Worker& worker, const cucumber::messages::gherkin_document& gherkinDocument, const assemble::AssembledTestCase& assembledTestCase, Context& testSuiteContext, bool& failing) + { + co_await tp->schedule(); + + try + { + failing |= !worker.RunTestCase(gherkinDocument, assembledTestCase, testSuiteContext, failing); + } + catch (...) + { + failing = true; + } + + tasksLatch.count_down(); + + co_return; + } + + struct ParallelBroadcaster : util::Broadcaster + { + ParallelBroadcaster(util::Broadcaster& broadcaster, std::unique_ptr& threadPool) + : broadcaster{ broadcaster } + , threadPool{ threadPool } + { + } + + void AddListener(util::Listener* listener) override + { + broadcaster.AddListener(listener); + } + + void RemoveListener(util::Listener* listener) override + { + broadcaster.RemoveListener(listener); + } + + void BroadcastEvent(const cucumber::messages::envelope& envelope) override + { + coro::sync_wait(queue.emplace(envelope)); + } + + coro::task BroadcastEventTask() + { + co_await threadPool->schedule(); + + while (true) + { + auto event = co_await queue.pop(); + + if (!event) + break; + + broadcaster.BroadcastEvent(*event); + } + } + + coro::task ShutdownTask(coro::latch& latch) + { + co_await threadPool->schedule(); + co_await latch; + co_await queue.shutdown_drain(threadPool); + co_return; + } + + private: + util::Broadcaster& broadcaster; + std::unique_ptr& threadPool; + + coro::queue queue; + }; + } + + ParallelRuntimeAdapter::ParallelRuntimeAdapter(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const std::list& sourcedPickles, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary& supportCodeLibrary, + Context& programContext) + : testRunStartedId{ std::move(testRunStartedId) } + , broadcaster{ broadcaster } + , idGenerator{ std::move(idGenerator) } + , sourcedPickles{ sourcedPickles } + , options{ options } + , supportCodeLibrary{ supportCodeLibrary } + , programContext{ programContext } + { + } + + bool ParallelRuntimeAdapter::Run() + { + std::unique_ptr workerThreadPool{ coro::thread_pool::make_unique(coro::thread_pool::options{ .thread_count = options.parallel }) }; + std::unique_ptr supportThreadPool{ coro::thread_pool::make_unique(coro::thread_pool::options{ .thread_count = 2 }) }; + + std::vector> tasks; + bool failing = false; + runtime::Worker synchronousWorker{ testRunStartedId, broadcaster, idGenerator, options, supportCodeLibrary, programContext }; + + failing |= IsFailing(util::GetWorstTestStepResult(synchronousWorker.RunBeforeAllHooks()).status, options.dryRun); + + if (!failing) + { + ParallelBroadcaster parallelBroadcaster{ broadcaster, supportThreadPool }; + tasks.emplace_back(parallelBroadcaster.BroadcastEventTask()); + + runtime::Worker parallelWorker{ testRunStartedId, parallelBroadcaster, idGenerator, options, supportCodeLibrary, programContext }; + + auto assembledTestSuites = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); + + coro::latch taskLatch{ std::ranges::distance(assembledTestSuites | std::views::transform([](const auto& suite) -> const std::list& + { + return suite.testCases; + }) | + std::views::join) }; + + for (const auto& assembledTestSuite : assembledTestSuites) + for (const auto& assembledTestCase : assembledTestSuite.testCases) + tasks.emplace_back(RunTestCase(workerThreadPool, taskLatch, parallelWorker, assembledTestSuite.gherkinDocument, assembledTestCase, programContext, failing)); + + tasks.emplace_back(parallelBroadcaster.ShutdownTask(taskLatch)); + + coro::sync_wait(coro::when_all(std::move(tasks))); + } + + failing |= IsFailing(util::GetWorstTestStepResult(synchronousWorker.RunAfterAllHooks()).status, options.dryRun); + + return !failing; + } +} diff --git a/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.hpp new file mode 100644 index 00000000..8bd63bde --- /dev/null +++ b/cucumber_cpp/library/runtime/ParallelRuntimeAdapter.hpp @@ -0,0 +1,37 @@ +#ifndef RUNTIME_PARALLEL_RUNTIME_ADAPTER_HPP +#define RUNTIME_PARALLEL_RUNTIME_ADAPTER_HPP + +#include "cucumber/gherkin/id_generator.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" +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct ParallelRuntimeAdapter : support::RuntimeAdapter + { + ParallelRuntimeAdapter(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const std::list& sourcedPickles, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary& supportCodeLibrary, + Context& programContext); + + bool Run() override; + + private: + std::string testRunStartedId; + util::Broadcaster& broadcaster; + cucumber::gherkin::id_generator_ptr idGenerator; + const std::list& sourcedPickles; + const support::RunOptions::Runtime& options; + support::SupportCodeLibrary& supportCodeLibrary; + Context& programContext; + }; +} + +#endif diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index c5249c1e..a0b9ee45 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -3,6 +3,7 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/Context.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" @@ -11,6 +12,7 @@ #include #include #include +#include namespace cucumber_cpp::library::runtime { @@ -32,9 +34,9 @@ namespace cucumber_cpp::library::runtime const support::RunOptions::Runtime& options, support::SupportCodeLibrary& supportCodeLibrary, Context& programContext) - : testRunStartedId{ testRunStartedId } + : testRunStartedId{ std::move(testRunStartedId) } , broadcaster{ broadcaster } - , idGenerator{ idGenerator } + , idGenerator{ std::move(idGenerator) } , sourcedPickles{ sourcedPickles } , options{ options } , supportCodeLibrary{ supportCodeLibrary } @@ -56,10 +58,9 @@ namespace cucumber_cpp::library::runtime auto assembledTestSuites = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); for (const auto& assembledTestSuite : assembledTestSuites) - { try { - const auto success = worker.RunTestSuite(assembledTestSuite, failing); + const auto success = RunTestSuite(worker, assembledTestSuite, failing); if (!success) failing = true; } @@ -67,7 +68,6 @@ namespace cucumber_cpp::library::runtime { failing = true; } - } } const auto afterHookResults = worker.RunAfterAllHooks(); @@ -77,4 +77,32 @@ namespace cucumber_cpp::library::runtime return !failing; } + + bool SerialRuntimeAdapter::RunTestSuite(runtime::Worker& worker, const assemble::AssembledTestSuite& assembledTestSuite, bool failing) + { + Context testSuiteContext{ &programContext }; + + auto failed = false; + + if (options.featureHooks) + { + const auto beforeHookResults = worker.RunBeforeTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); + + if (IsFailing(util::GetWorstTestStepResult(beforeHookResults).status, options.dryRun)) + failing = true; + } + + for (const auto& assembledTestCase : assembledTestSuite.testCases) + failed |= !worker.RunTestCase(assembledTestSuite.gherkinDocument, assembledTestCase, testSuiteContext, failed || failing); + + if (options.featureHooks) + { + const auto afterHookResults = worker.RunAfterTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); + + if (IsFailing(util::GetWorstTestStepResult(afterHookResults).status, options.dryRun)) + failing = true; + } + + return !failed; + } } diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp index 0321b3ec..d20f07be 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -3,6 +3,8 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/library/Context.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" @@ -24,6 +26,8 @@ namespace cucumber_cpp::library::runtime bool Run() override; private: + bool RunTestSuite(runtime::Worker& worker, const assemble::AssembledTestSuite& assembledTestSuite, bool failing); + std::string testRunStartedId; util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 2729591d..bc892490 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -10,14 +10,11 @@ #include "cucumber/messages/test_step_result_status.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/runtime/TestCaseRunner.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/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" #include "cucumber_cpp/library/util/HookData.hpp" #include "cucumber_cpp/library/util/Timestamp.hpp" #include "cucumber_cpp/library/util/TransformHookData.hpp" @@ -28,7 +25,6 @@ #include "fmt/format.h" #include #include -#include #include #include #include @@ -49,21 +45,11 @@ namespace cucumber_cpp::library::runtime std::size_t RetriesForPickle(const cucumber::messages::pickle& pickle, const support::RunOptions::Runtime& options) { - if (options.retry == 0) - return 0; - else if (options.retryTagExpression->Evaluate(util::TransformPickleTags(pickle.tags))) + if (options.retry != 0 && options.retryTagExpression->Evaluate(util::TransformPickleTags(pickle.tags))) return options.retry; else return 0; } - - bool IsFailing(cucumber::messages::test_step_result_status status, bool dryRun) - { - if (dryRun) - return false; - - return status != cucumber::messages::test_step_result_status::PASSED; - } } Worker::Worker(std::string_view testRunStartedId, @@ -103,34 +89,6 @@ namespace cucumber_cpp::library::runtime return results; } - bool Worker::RunTestSuite(const assemble::AssembledTestSuite& assembledTestSuite, bool failing) - { - Context testSuiteContext{ &programContext }; - - auto failed = false; - - if (options.featureHooks) - { - const auto beforeHookResults = RunBeforeTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); - - if (IsFailing(util::GetWorstTestStepResult(beforeHookResults).status, options.dryRun)) - failing = true; - } - - for (const auto& assembledTestCase : assembledTestSuite.testCases) - failed |= !RunTestCase(assembledTestSuite.gherkinDocument, assembledTestCase, testSuiteContext, failed || failing); - - if (options.featureHooks) - { - const auto afterHookResults = RunAfterTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); - - if (IsFailing(util::GetWorstTestStepResult(afterHookResults).status, options.dryRun)) - failing = true; - } - - return !failed; - } - bool Worker::RunTestCase(const cucumber::messages::gherkin_document& gherkinDocument, const assemble::AssembledTestCase& assembledTestCase, Context& testSuiteContext, bool failing) { TestCaseRunner testCaseRunner{ @@ -186,7 +144,7 @@ namespace cucumber_cpp::library::runtime .timestamp = util::TimestampNow(), }; - broadcaster.BroadcastEvent({ .test_run_hook_started = testRunHookStarted }); + broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_run_hook_started = testRunHookStarted }); cucumber::messages::test_step_result result{ .duration{ .seconds = 0, .nanos = 0 }, .status = cucumber::messages::test_step_result_status::SKIPPED }; if (!options.dryRun) @@ -197,11 +155,11 @@ namespace cucumber_cpp::library::runtime throw GlobalHookError{ fmt::format("Global Hook Failed: {}\nresult:{}", util::TransformHookData(definition.data).to_string(), result.to_string()) }; } - broadcaster.BroadcastEvent({ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ - .test_run_hook_started_id = testRunHookStartedId, - .result = result, - .timestamp = util::TimestampNow(), - } }); + broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ + .test_run_hook_started_id = testRunHookStartedId, + .result = result, + .timestamp = util::TimestampNow(), + } }); return result; } diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp index bf22a53e..3b864440 100644 --- a/cucumber_cpp/library/runtime/Worker.hpp +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -37,16 +37,15 @@ namespace cucumber_cpp::library::runtime 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); + bool RunTestCase(const cucumber::messages::gherkin_document& gherkinDocument, const assemble::AssembledTestCase& assembledTestCase, Context& testSuiteContext, bool failing); + + private: cucumber::messages::test_step_result RunTestHook(const std::string& id, Context& context); - bool IsStatusFailed(cucumber::messages::test_step_result_status status) const; + [[nodiscard]] bool IsStatusFailed(cucumber::messages::test_step_result_status status) const; std::string_view testRunStartedId; util::Broadcaster& broadcaster; diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp index 865aaa08..cc0ea2b3 100644 --- a/cucumber_cpp/library/support/Types.hpp +++ b/cucumber_cpp/library/support/Types.hpp @@ -6,6 +6,7 @@ #include "cucumber/messages/pickle.hpp" #include "cucumber_cpp/library/tag_expression/Model.hpp" #include +#include #include #include #include @@ -17,7 +18,7 @@ namespace cucumber_cpp::library::support { struct RunOptions { - enum class Ordering + enum class Ordering : std::uint8_t { defined, reverse, @@ -25,7 +26,7 @@ namespace cucumber_cpp::library::support struct Sources { - std::set> paths{}; + std::set> paths; std::unique_ptr tagExpression; Ordering ordering{ Ordering::defined }; @@ -38,8 +39,9 @@ namespace cucumber_cpp::library::support bool failGlobalHookFast{ false }; std::size_t retry{ 0 }; bool strict{ true }; - std::unique_ptr retryTagExpression{}; + std::unique_ptr retryTagExpression; bool featureHooks{ false }; + std::uint32_t parallel{ 0 }; } runtime; struct RunEnvironment diff --git a/cucumber_cpp/library/util/Broadcaster.cpp b/cucumber_cpp/library/util/Broadcaster.cpp index a3932f20..d9e25226 100644 --- a/cucumber_cpp/library/util/Broadcaster.cpp +++ b/cucumber_cpp/library/util/Broadcaster.cpp @@ -23,19 +23,19 @@ namespace cucumber_cpp::library::util onEvent(envelope); } - void Broadcaster::AddListener(Listener* listener) + void BroadcasterImpl::AddListener(Listener* listener) { listeners.push_back(listener); } - void Broadcaster::RemoveListener(Listener* listener) + void BroadcasterImpl::RemoveListener(Listener* listener) { std::erase(listeners, listener); } - void Broadcaster::BroadcastEvent(const cucumber::messages::envelope& envelope) + void BroadcasterImpl::BroadcastEvent(const cucumber::messages::envelope& envelope) { - for (auto& listener : listeners) + for (const auto& listener : listeners) listener->Invoke(envelope); } } diff --git a/cucumber_cpp/library/util/Broadcaster.hpp b/cucumber_cpp/library/util/Broadcaster.hpp index 15f55281..4ff9a961 100644 --- a/cucumber_cpp/library/util/Broadcaster.hpp +++ b/cucumber_cpp/library/util/Broadcaster.hpp @@ -33,10 +33,20 @@ namespace cucumber_cpp::library::util struct Broadcaster { - void AddListener(Listener* listener); - void RemoveListener(Listener* listener); + virtual ~Broadcaster() = default; - void BroadcastEvent(const cucumber::messages::envelope& envelope); + virtual void AddListener(Listener* listener) = 0; + virtual void RemoveListener(Listener* listener) = 0; + + virtual void BroadcastEvent(const cucumber::messages::envelope& envelope) = 0; + }; + + struct BroadcasterImpl : Broadcaster + { + void AddListener(Listener* listener) override; + void RemoveListener(Listener* listener) override; + + void BroadcastEvent(const cucumber::messages::envelope& envelope) override; private: std::vector listeners; diff --git a/cucumber_cpp/library/util/Timestamp.hpp b/cucumber_cpp/library/util/Timestamp.hpp index a4ab865a..8a483095 100644 --- a/cucumber_cpp/library/util/Timestamp.hpp +++ b/cucumber_cpp/library/util/Timestamp.hpp @@ -9,9 +9,9 @@ namespace cucumber_cpp::library::util { - constexpr std::size_t millisecondsPerSecond = 1e3; - constexpr std::size_t nanosecondsPerMillisecond = 1e6; - constexpr std::size_t nanosecondsPerSecond = 1e9; + constexpr std::size_t millisecondsPerSecond{ 1000u }; + constexpr std::size_t nanosecondsPerMillisecond{ 1000000u }; + constexpr std::size_t nanosecondsPerSecond{ 1000000000u }; struct TimestampGenerator { diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 6de30b50..99c5ebd6 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -12,3 +12,7 @@ if (CCR_BUILD_TESTS) endif() add_subdirectory(tobiaslocker) add_subdirectory(zeux) + +if (CCR_ENABLE_PARALLEL_SUPPORT) + add_subdirectory(jbaldwin) +endif() diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index da3b0381..e415cc10 100644 --- a/external/cucumber/gherkin/CMakeLists.txt +++ b/external/cucumber/gherkin/CMakeLists.txt @@ -1,14 +1,9 @@ FetchContent_Declare( cucumber_gherkin GIT_REPOSITORY https://github.com/cucumber/gherkin.git - GIT_TAG 6fe0a3be9df9388209cca37bc3f254be10a6014e # v39.0.0 + GIT_TAG 9f99514f288381e0f614cfb74c856710f1584574 # unreleased main ) FetchContent_MakeAvailable(cucumber_gherkin) add_subdirectory(${cucumber_gherkin_SOURCE_DIR}/cpp ${cucumber_gherkin_BINARY_DIR}/cpp) - -# The usage of designated initializers requires c++20 in MSVC -set_target_properties(cucumber_gherkin_lib PROPERTIES CXX_STANDARD 20) -set_target_properties(cucumber_gherkin_bin PROPERTIES CXX_STANDARD 20) -set_target_properties(cucumber_gherkin_generate_tokens_bin PROPERTIES CXX_STANDARD 20) diff --git a/external/cucumber/messages/CMakeLists.txt b/external/cucumber/messages/CMakeLists.txt index 8d6f34ef..7122f3b8 100644 --- a/external/cucumber/messages/CMakeLists.txt +++ b/external/cucumber/messages/CMakeLists.txt @@ -8,8 +8,3 @@ FetchContent_Declare( FetchContent_MakeAvailable(cucumber_messages) add_subdirectory(${cucumber_messages_SOURCE_DIR}/cpp ${cucumber_messages_BINARY_DIR}/cpp) - -target_compile_options(cucumber_messages_lib PUBLIC - # cucumber_gherkin-src/cpp/include/gherkin/cucumber/gherkin/utils.hpp:34:31: error: 'codecvt_utf8' is deprecated - $<$:-Wno-deprecated-declarations> -) diff --git a/external/jbaldwin/CMakeLists.txt b/external/jbaldwin/CMakeLists.txt new file mode 100644 index 00000000..04f36133 --- /dev/null +++ b/external/jbaldwin/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(libcoro) diff --git a/external/jbaldwin/libcoro/CMakeLists.txt b/external/jbaldwin/libcoro/CMakeLists.txt new file mode 100644 index 00000000..45a91ad7 --- /dev/null +++ b/external/jbaldwin/libcoro/CMakeLists.txt @@ -0,0 +1,14 @@ + +FetchContent_Declare( + libcoro + # GIT_REPOSITORY https://github.com/jbaldwin/libcoro.git + GIT_REPOSITORY https://github.com/daantimmer/libcoro.git + GIT_TAG 9b2182cedbb95a563025b1a06d38b3989097c5d6 # public contribution on forked repository fixing warnings + SYSTEM +) + +set(LIBCORO_FEATURE_TLS OFF CACHE STRING "") +set(LIBCORO_BUILD_TESTS OFF CACHE STRING "") +set(LIBCORO_BUILD_EXAMPLES OFF CACHE STRING "") + +FetchContent_MakeAvailable(libcoro)