Skip to content

Commit f031eec

Browse files
authored
feat!: full support for cucumber expressions (#180)
* feat: full support for cucumber expressions Fixes #178 * revert C++ standard to 20 and improve error handling in expression evaluation * fix: remove unused print include from TestExpression.cpp * try fix mac build * fixing mac builds * add unicode test * refactor: remove unused regex includes and commented-out code * feat: add error handling for optional and alternative expressions * fix: add empty block comment for tuple step definition * fix: mark NoEligibleParsers constructor as explicit * feat: add InvalidTokenType exception for better error handling * fix: correct move semantics for stepRegexStr in StepMatch constructor * feat: add InvalidNodeType exception and refactor regex handling in Expression class * refactor: improve parameter passing in ExpressionParser and SubParser methods * refactor: change character type from char to unsigned char in Token methods for consistency * fix: include ParameterRegistry header and update converter initialization in Expression class * refactor: rename InHelper to MatchTokenHelper for clarity and consistency * refactor: update Node and Token classes for improved encapsulation and consistency * refactor: simplify Result construction in ExpressionParser methods for clarity * fix: remove redundant check for line start and end tokens in ParseTokensUntil method * refactor: enhance Converter constructor for improved clarity and functionality * refactor: update parser state references for consistency and clarity * refactor: remove default constructor from ExpressionTokenizer * fix: update PatternVisitor usage in StepRegistry and remove unused visitor instances in Matcher * fix: replace 'and' with '&&' for logical operation in ExpressionTokenizer * refactor: simplify empty expression handling in ExpressionTokenizer * refactor: update ParseTokensUntil and ParseToken signatures for consistency * refactor: update Converter constructor signature for consistency * fix: update ccache key and add crt-version to get-winsdk.sh for consistency * refactor: update HookExecutor and TestExecution to use optional scopes for feature and scenario starts * test: test cucumber expressions against official test input files * fix: update YAML file loading to use string path for consistency * refactor: update GetTestData function to use std::filesystem::path for improved path handling * fix: add missing custom command to copy testdata directory for cucumber expression tests * refactor: remove debug output from parameter registry match function * fix: add const qualifier to ParseBetweenGenerator method for consistency * fix: rename variables for clarity and consistency in ExpressionTokenizer * fix: correct text parameter passing in MatchVisitor to avoid unnecessary move * fix: remove unnecessary semicolon and improve error message in StringTo function * fix: remove ContextManager parameter from FeatureStart and ScenarioStart methods for cleaner interface * fix: correct ignore list in .ls-lint.yml for accurate linting configuration * fix: add megalinter-reports to ignore list for improved linting configuration * fix: add excluded directories to .mega-linter.yml for improved linting configuration * test: add boolean matching tests to TestExpression for improved coverage * fix: remove debug output from TestExpressionParser for cleaner test output * test: add Token::NameOf tests for comprehensive coverage in TestExpressionTokenizer * fix: update .mega-linter.yml to disable REPOSITORY_CHECKOV linter * fix: add testdata directory to excluded directories in .mega-linter.yml * fix: update BODY macro to use static_cast for function pointer type in Execute method * refactor: integrate StepRegistry and FeatureTreeFactory into Application and TestRunner implementations * refactor: replace std::int64_t with std::int32_t in ParameterRegistry and related steps * fix: remove commented out add_subdirectory for test_helper in CMakeLists.txt * fix: improve error message formatting and add test for unknown parameter type * refactor: improve formatting of error struct definitions for better readability * fix: optimize match iteration logic in Expression class * refactor: rename methods for clarity and consistency in Expression class * chore: only execute hooks when there was no failure previously * feat: add CurrentContext method to ScopedFeatureContext and ExecutionStatus methods to ProgramScope and FeatureScope * chore: remove unneccsary complexity * chore: remove unused includes and commented code in HookExecutor * refactor: add HasSetUpTearDown concept * refactor: change FeatureTreeFactory and Application methods to use const references * feat: enhance error handling in Application and add overloaded GetExitCode method * refactor: use const references for MatchRange in converter functions * feat: improve error handling in Application and FeatureFactory with specific exceptions * refactor: remove unused executeHooks member from ScopedHook * fix: correct path conversion in UnsupportedAsteriskError exception * fix: simplify exception handling in CreateStepInfo method * refactor: consolidate error handling by replacing InternalError with Errors.hpp * refactor: remove unused functions
1 parent 1c7fcde commit f031eec

File tree

173 files changed

+3943
-426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

173 files changed

+3943
-426
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
FROM ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86
1+
FROM ghcr.io/philips-software/amp-devcontainer-cpp:6.0.1@sha256:0b238dbdd0e39a9704fc5317269f654b8bfd23868bad9491e806ae417d645352
22

33
HEALTHCHECK NONE

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
id: cache-winsdk
3232
with:
3333
path: /winsdk
34-
key: cache-winsdk
34+
key: cache-winsdk-10.0.26100-14.43.17.13
3535
- if: ${{ steps.cache-winsdk.outputs.cache-hit != 'true' }}
3636
run: ./get-winsdk.sh
3737
- uses: hendrikmuhs/ccache-action@a1209f81afb8c005c13b4296c32e363431bffea5 # v1.2.17

.ls-lint.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ ls:
44
.cpp: PascalCase
55

66
ignore:
7+
- .build
78
- .devcontainer
89
- .git
910
- .github
1011
- .vscode
11-
- extern
12+
- external
1213
- megalinter-reports
14+
- testdata

.mega-linter.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ ENABLE:
1010
- YAML
1111
DISABLE_LINTERS:
1212
- CPP_CPPLINT
13+
- JSON_V8R
14+
- REPOSITORY_CHECKOV
1315
- REPOSITORY_DEVSKIM
1416
- REPOSITORY_GITLEAKS
1517
- REPOSITORY_KICS
1618
- REPOSITORY_SEMGREP
1719
- SPELL_CSPELL
18-
- JSON_V8R
1920
DISABLE_ERRORS_LINTERS:
2021
- MARKDOWN_MARKDOWN_LINK_CHECK
2122
- SPELL_PROSELINT
@@ -27,3 +28,4 @@ PRINT_ALPACA: false
2728
SHOW_SKIPPED_LINTERS: false
2829
REPOSITORY_GOODCHECK_RULES_PATH: .github/linters
2930
JSON_JSONLINT_ARGUMENTS: --mode cjson
31+
EXCLUDED_DIRECTORIES: [testdata]

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ if (CCR_BUILD_TESTS)
3333
ccr_enable_testing()
3434
endif()
3535

36-
set(CMAKE_CXX_STANDARD 23)
36+
set(CMAKE_CXX_STANDARD 20)
3737
set(CMAKE_CXX_STANDARD_REQUIRED On)
3838
set(CMAKE_CXX_EXTENSIONS Off)
3939

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@unicode
2+
Feature: Test for unicode characters
3+
@result:undefined
4+
Scenario: Can match unicode characters
5+
Given a ← tuple(4.3, -4.2, 3.1, 1.0)

cucumber_cpp/acceptance_test/steps/Steps.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "cucumber_cpp/library/Steps.hpp"
22
#include "gmock/gmock.h"
33
#include "gtest/gtest.h"
4+
#include <cstdint>
45
#include <iostream>
56
#include <string>
67

@@ -45,7 +46,7 @@ THEN("two expectations are raised")
4546
EXPECT_THAT(1, testing::Eq(0));
4647
}
4748

48-
WHEN("I print {string} with value {int}", (const std::string& str, std::int32_t value))
49+
GIVEN("a ← tuple\\({float}, {float}, {float}, {float}\\)", (float /* unused */, float /* unused */, float /* unused */, float /* unused */))
4950
{
50-
std::cout << "print: " << str << " with value " << value;
51+
// empty
5152
}

cucumber_cpp/acceptance_test/test.bats

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,13 @@ teardown() {
181181
@test "Test error program hook results in error and skipped steps" {
182182
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
183183
assert_failure
184-
assert_output --partial "skipped Given a given step"
184+
refute_output --partial "skipped Given a given step"
185185
refute_output --partial "should not be executed"
186-
# assert_output --partial "tests : 0/1 passed"
186+
assert_output --partial "tests : 0/0 passed"
187+
}
188+
189+
@test "Test unicode" {
190+
run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unicode" --report console
191+
assert_success
192+
assert_output --partial "tests : 1/1 passed"
187193
}

cucumber_cpp/library/Application.cpp

Lines changed: 56 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "cucumber_cpp/library/Application.hpp"
22
#include "cucumber_cpp/library/Context.hpp"
3+
#include "cucumber_cpp/library/Errors.hpp"
4+
#include "cucumber_cpp/library/StepRegistry.hpp"
5+
#include "cucumber_cpp/library/cucumber_expression/Errors.hpp"
36
#include "cucumber_cpp/library/engine/ContextManager.hpp"
47
#include "cucumber_cpp/library/engine/FeatureFactory.hpp"
58
#include "cucumber_cpp/library/engine/FeatureInfo.hpp"
@@ -16,6 +19,7 @@
1619
#include <CLI/impl/App_inl.hpp>
1720
#include <CLI/impl/Option_inl.hpp>
1821
#include <algorithm>
22+
#include <exception>
1923
#include <filesystem>
2024
#include <functional>
2125
#include <iostream>
@@ -27,23 +31,14 @@
2731
#include <ranges>
2832
#include <string>
2933
#include <string_view>
34+
#include <type_traits>
3035
#include <utility>
3136
#include <vector>
3237

3338
namespace cucumber_cpp::library
3439
{
3540
namespace
3641
{
37-
std::string_view const_char_to_sv(const char* value)
38-
{
39-
return { value };
40-
}
41-
42-
std::string_view subrange_to_sv(const auto& subrange)
43-
{
44-
return { std::data(subrange), std::data(subrange) + std::size(subrange) };
45-
}
46-
4742
template<class Range>
4843
std::string Join(const Range& range, std::string_view delim)
4944
{
@@ -52,26 +47,16 @@ namespace cucumber_cpp::library
5247

5348
return std::accumulate(std::next(range.begin()), range.end(), range.front(), [&delim](const auto& lhs, const auto& rhs)
5449
{
55-
std::string join;
56-
join.reserve(lhs.size() + delim.size() + rhs.size());
57-
join.append(lhs);
58-
join.append(delim);
59-
join.append(rhs);
60-
return join;
50+
return std::format("{}{}{}", lhs, delim, rhs);
6151
});
6252
}
6353

64-
std::string JoinStringWithSpace(const std::string& a, const std::string& b)
65-
{
66-
return a + ' ' + b;
67-
}
68-
69-
std::filesystem::path to_fs_path(const std::string_view& sv)
54+
std::filesystem::path ToFileSystemPath(const std::string_view& sv)
7055
{
7156
return { sv };
7257
}
7358

74-
bool is_feature_file(const std::filesystem::directory_entry& entry)
59+
bool IsFeatureFile(const std::filesystem::directory_entry& entry)
7560
{
7661
return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature";
7762
}
@@ -80,9 +65,9 @@ namespace cucumber_cpp::library
8065
{
8166
std::vector<std::filesystem::path> files;
8267

83-
for (const auto feature : options.features | std::views::transform(to_fs_path))
68+
for (const auto feature : options.features | std::views::transform(ToFileSystemPath))
8469
if (std::filesystem::is_directory(feature))
85-
for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(is_feature_file))
70+
for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile))
8671
files.emplace_back(entry.path());
8772
else
8873
files.emplace_back(feature);
@@ -102,26 +87,7 @@ namespace cucumber_cpp::library
10287
else
10388
return std::string{};
10489
})
105-
{
106-
}
107-
108-
ResultStatus& ResultStatus::operator=(Result result)
109-
{
110-
if ((resultStatus == Result::undefined || resultStatus == Result::passed) && result != Result::undefined)
111-
resultStatus = result;
112-
113-
return *this;
114-
}
115-
116-
ResultStatus::operator Result() const
117-
{
118-
return resultStatus;
119-
}
120-
121-
bool ResultStatus::IsSuccess() const
122-
{
123-
return resultStatus == Result::passed;
124-
}
90+
{}
12591

12692
Application::Application(std::shared_ptr<ContextStorageFactory> contextStorageFactory)
12793
: contextManager{ std::move(contextStorageFactory) }
@@ -168,6 +134,35 @@ namespace cucumber_cpp::library
168134
{
169135
return cli.exit(e);
170136
}
137+
catch (const InternalError& error)
138+
{
139+
std::cout << "Internal error:\n\n";
140+
std::cout << error.what() << std::endl;
141+
return GetExitCode(engine::Result::failed);
142+
}
143+
catch (const UnsupportedAsteriskError& error)
144+
{
145+
std::cout << "UnsupportedAsteriskError error:\n\n";
146+
std::cout << error.what() << std::endl;
147+
return GetExitCode(engine::Result::failed);
148+
}
149+
catch (const cucumber_expression::Error& error)
150+
{
151+
std::cout << "Cucumber Expression error:\n\n";
152+
std::cout << error.what() << std::endl;
153+
return GetExitCode(engine::Result::failed);
154+
}
155+
catch (const std::exception& error)
156+
{
157+
std::cout << "Generic error:\n\n";
158+
std::cout << error.what() << std::endl;
159+
return GetExitCode(engine::Result::failed);
160+
}
161+
catch (...)
162+
{
163+
std::cout << "Unknown error";
164+
return GetExitCode(engine::Result::failed);
165+
}
171166

172167
return GetExitCode();
173168
}
@@ -194,24 +189,25 @@ namespace cucumber_cpp::library
194189

195190
auto tagExpression = Join(options.tags, " ");
196191
engine::HookExecutorImpl hookExecution{ contextManager };
197-
engine::TestExecutionImpl testExecution{ contextManager, reporters, hookExecution, [this]() -> const engine::TestExecution::Policy&
198-
{
199-
if (options.dryrun)
200-
return engine::dryRunPolicy;
201-
else
202-
return engine::executeRunPolicy;
203-
}() };
204192

205-
engine::TestRunnerImpl testRunner{ testExecution };
193+
const auto& runPolicy = (options.dryrun) ? static_cast<const engine::TestExecution::Policy&>(engine::dryRunPolicy)
194+
: static_cast<const engine::TestExecution::Policy&>(engine::executeRunPolicy);
195+
engine::TestExecutionImpl testExecution{ contextManager, reporters, hookExecution, runPolicy };
196+
197+
StepRegistry stepRegistry{ parameterRegistry };
198+
engine::FeatureTreeFactory featureTreeFactory{ stepRegistry };
206199

207-
testRunner.Run(GetFeatureTree(tagExpression));
200+
engine::TestRunnerImpl testRunner{ featureTreeFactory, testExecution };
201+
202+
testRunner.Run(GetFeatureTree(featureTreeFactory, tagExpression));
208203

209204
std::cout << '\n'
210205
<< std::flush;
211206
}
212207

213-
std::vector<std::unique_ptr<engine::FeatureInfo>> Application::GetFeatureTree(std::string_view tagExpression)
208+
std::vector<std::unique_ptr<engine::FeatureInfo>> Application::GetFeatureTree(const engine::FeatureTreeFactory& featureTreeFactory, std::string_view tagExpression)
214209
{
210+
215211
const auto featureFiles = GetFeatureFiles(options);
216212
std::vector<std::unique_ptr<engine::FeatureInfo>> vec;
217213
vec.reserve(featureFiles.size());
@@ -224,10 +220,11 @@ namespace cucumber_cpp::library
224220

225221
int Application::GetExitCode() const
226222
{
227-
if (contextManager.ProgramContext().EffectiveExecutionStatus() == engine::Result::passed)
228-
return 0;
229-
else
230-
return 1;
223+
return GetExitCode(contextManager.ProgramContext().ExecutionStatus());
231224
}
232225

226+
int Application::GetExitCode(engine::Result result) const
227+
{
228+
return static_cast<std::underlying_type_t<engine::Result>>(result) - static_cast<std::underlying_type_t<engine::Result>>(engine::Result::passed);
229+
}
233230
}

cucumber_cpp/library/Application.hpp

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "cucumber/gherkin/app.hpp"
55
#include "cucumber_cpp/library/Context.hpp"
6+
#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp"
67
#include "cucumber_cpp/library/engine/ContextManager.hpp"
78
#include "cucumber_cpp/library/engine/FeatureFactory.hpp"
89
#include "cucumber_cpp/library/engine/FeatureInfo.hpp"
@@ -24,19 +25,6 @@ namespace cucumber_cpp::library
2425
explicit ReportHandlerValidator(const report::Reporters& reporters);
2526
};
2627

27-
struct ResultStatus
28-
{
29-
using Result = engine::Result;
30-
31-
ResultStatus& operator=(Result result);
32-
explicit operator Result() const;
33-
34-
[[nodiscard]] bool IsSuccess() const;
35-
36-
private:
37-
Result resultStatus{ Result::undefined };
38-
};
39-
4028
struct Application
4129
{
4230
struct Options
@@ -62,9 +50,10 @@ namespace cucumber_cpp::library
6250

6351
private:
6452
[[nodiscard]] int GetExitCode() const;
53+
[[nodiscard]] int GetExitCode(engine::Result result) const;
6554
void DryRunFeatures();
6655
void RunFeatures();
67-
[[nodiscard]] std::vector<std::unique_ptr<engine::FeatureInfo>> GetFeatureTree(std::string_view tagExpression);
56+
[[nodiscard]] std::vector<std::unique_ptr<engine::FeatureInfo>> GetFeatureTree(const engine::FeatureTreeFactory& featureTreeFactory, std::string_view tagExpression);
6857
[[nodiscard]] engine::Result RunFeature(const std::filesystem::path& path, std::string_view tagExpression, report::ReportHandlerV2& reportHandler);
6958

7059
Options options;
@@ -78,7 +67,7 @@ namespace cucumber_cpp::library
7867

7968
cucumber::gherkin::app gherkin;
8069

81-
engine::FeatureTreeFactory featureTreeFactory{};
70+
cucumber_expression::ParameterRegistry parameterRegistry;
8271
};
8372
}
8473

0 commit comments

Comments
 (0)