Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,12 @@ endif()

option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." ${CCR_DEFAULTOPT} )
option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT})
option(CCR_USE_RE2 "Use the RE2 regex engine (fetched when CCR_FETCH_DEPS is set, otherwise auto-detected)" ${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)

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

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)
endif()
endif()

if (CCR_BUILD_TESTS)
ccr_enable_testing()
endif()
Expand Down Expand Up @@ -81,6 +75,26 @@ else()
if (CCR_BUILD_TESTS)
find_package(yaml-cpp REQUIRED)
endif()

find_package(absl)
find_package(re2)
endif()

if (TARGET re2::re2)
set(CCR_HAS_RE2 On)
else()
set(CCR_HAS_RE2 Off)
endif()

# Sanitizer flags are set after external dependencies so that fetched third-party
# libraries (abseil, re2) are not compiled with sanitizer instrumentation.
# Abseil's constexpr function-pointer comparisons are not compatible with GCC's
# ASan/UBSan instrumentation, which causes build failures.
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)
endif()
endif()

add_subdirectory(cucumber_cpp)
Expand Down
20 changes: 20 additions & 0 deletions cucumber_cpp/library/cucumber_expression/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE
MatchRange.hpp
ParameterRegistry.cpp
ParameterRegistry.hpp
RegexStrategy.hpp
RegexStrategyFactory.cpp
RegexStrategyFactory.hpp
RegularExpression.cpp
RegularExpression.hpp
StdRegexStrategy.cpp
StdRegexStrategy.hpp
TreeRegexp.cpp
TreeRegexp.hpp
)
Expand All @@ -33,6 +38,21 @@ target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC
fmt::fmt
)

if (CCR_HAS_RE2)
target_sources(cucumber_cpp.library.cucumber_expression PRIVATE
Re2RegexStrategy.cpp
Re2RegexStrategy.hpp
)

target_compile_definitions(cucumber_cpp.library.cucumber_expression PUBLIC
CCR_HAS_RE2
)

target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC
re2::re2
)
endif()

if (CCR_BUILD_TESTS)
add_subdirectory(test)
endif()
36 changes: 36 additions & 0 deletions cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp"
#include <re2/re2.h>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MegaLinter] reported by reviewdog 🐶

Suggested change
#include <re2/re2.h>

#include <optional>
#include <stdexcept>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MegaLinter] reported by reviewdog 🐶

Suggested change
#include <stdexcept>
#include <re2/re2.h>
#include <stdexcept>

#include <string>
#include <string_view>
#include <vector>

namespace cucumber_cpp::library::cucumber_expression
{
Re2RegexStrategy::Re2RegexStrategy(std::string_view pattern)
: re2{ std::make_unique<re2::RE2>(pattern) }
{
if (!re2->ok())
throw std::invalid_argument(re2->error());
}

Re2RegexStrategy::~Re2RegexStrategy() = default;

std::optional<std::vector<std::string>> Re2RegexStrategy::Match(std::string_view text) const
{
const int nCaptures = re2->NumberOfCapturingGroups();
const int nSubmatch = nCaptures + 1;
std::vector<absl::string_view> submatch(static_cast<std::size_t>(nSubmatch));

if (!re2->Match(text, 0, static_cast<int>(text.size()), RE2::UNANCHORED, submatch.data(), nSubmatch))
return std::nullopt;

std::vector<std::string> result;
result.reserve(static_cast<std::size_t>(nSubmatch));
for (const auto& piece : submatch)
result.emplace_back(piece.data(), piece.size());

return result;
}
}
30 changes: 30 additions & 0 deletions cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef CUCUMBER_EXPRESSION_RE2REGEXSTRATEGY_HPP
#define CUCUMBER_EXPRESSION_RE2REGEXSTRATEGY_HPP

#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace re2
{
class RE2;
}

namespace cucumber_cpp::library::cucumber_expression
{
struct Re2RegexStrategy : RegexStrategy
{
explicit Re2RegexStrategy(std::string_view pattern);
~Re2RegexStrategy() override;

[[nodiscard]] std::optional<std::vector<std::string>> Match(std::string_view text) const override;

private:
std::unique_ptr<re2::RE2> re2;
};
}

#endif
2 changes: 2 additions & 0 deletions cucumber_cpp/library/cucumber_expression/RegexStrategy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file has been superseded by StdRegexStrategy.cpp and Re2RegexStrategy.cpp.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MegaLinter] reported by reviewdog 🐶

Suggested change

24 changes: 24 additions & 0 deletions cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP
#define CUCUMBER_EXPRESSION_REGEXSTRATEGY_HPP

#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace cucumber_cpp::library::cucumber_expression
{
struct RegexStrategy
{
RegexStrategy() = default;
RegexStrategy(const RegexStrategy&) = delete;
RegexStrategy& operator=(const RegexStrategy&) = delete;
RegexStrategy(RegexStrategy&&) = delete;
RegexStrategy& operator=(RegexStrategy&&) = delete;
virtual ~RegexStrategy() = default;

[[nodiscard]] virtual std::optional<std::vector<std::string>> Match(std::string_view text) const = 0;
};
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "cucumber_cpp/library/cucumber_expression/RegexStrategyFactory.hpp"
#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp"
#ifdef CCR_HAS_RE2
#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp"
#endif
#include <memory>
#include <string_view>

namespace cucumber_cpp::library::cucumber_expression
{
std::unique_ptr<RegexStrategy> CreateRegexStrategy(std::string_view pattern)
{
#ifdef CCR_HAS_RE2
return std::make_unique<Re2RegexStrategy>(pattern);
#else
return std::make_unique<StdRegexStrategy>(pattern);
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef CUCUMBER_EXPRESSION_REGEXSTRATEGYFACTORY_HPP
#define CUCUMBER_EXPRESSION_REGEXSTRATEGYFACTORY_HPP

#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp"
#include <memory>
#include <string_view>

namespace cucumber_cpp::library::cucumber_expression
{
[[nodiscard]] std::unique_ptr<RegexStrategy> CreateRegexStrategy(std::string_view pattern);
}

#endif
29 changes: 29 additions & 0 deletions cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp"
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <vector>

namespace cucumber_cpp::library::cucumber_expression
{
StdRegexStrategy::StdRegexStrategy(std::string_view pattern)
: regex{ std::string(pattern) }
{
}

std::optional<std::vector<std::string>> StdRegexStrategy::Match(std::string_view text) const
{
std::smatch match;
const std::string textStr(text);
if (!std::regex_search(textStr, match, regex))

Check warning on line 19 in cucumber_cpp/library/cucumber_expression/StdRegexStrategy.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "textStr" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=philips-software_amp-cucumber-cpp-runner&issues=AZ4xDVrnyEig4NsZiaT8&open=AZ4xDVrnyEig4NsZiaT8&pullRequest=336
return std::nullopt;

std::vector<std::string> result;
result.reserve(match.size());
for (const auto& m : match)
result.emplace_back(m.str());

return result;
}
}
24 changes: 24 additions & 0 deletions cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef CUCUMBER_EXPRESSION_STDREGEXSTRATEGY_HPP
#define CUCUMBER_EXPRESSION_STDREGEXSTRATEGY_HPP

#include "cucumber_cpp/library/cucumber_expression/RegexStrategy.hpp"
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <vector>

namespace cucumber_cpp::library::cucumber_expression
{
struct StdRegexStrategy : RegexStrategy
{
explicit StdRegexStrategy(std::string_view pattern);

[[nodiscard]] std::optional<std::vector<std::string>> Match(std::string_view text) const override;

private:
std::regex regex;
};
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE
TestExpression.cpp
TestExpressionParser.cpp
TestExpressionTokenizer.cpp
TestRegexStrategy.cpp
TestRegexStrategyFactory.cpp
TestRegularExpression.cpp
TestTransformation.cpp
TestTreeRegexp.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "cucumber_cpp/library/cucumber_expression/StdRegexStrategy.hpp"
#ifdef CCR_HAS_RE2
#include "cucumber_cpp/library/cucumber_expression/Re2RegexStrategy.hpp"
#endif
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>

namespace cucumber_cpp::library::cucumber_expression
{
template<typename T>
struct TestRegexStrategy : testing::Test
{};

#ifdef CCR_HAS_RE2
using RegexStrategyTypes = testing::Types<StdRegexStrategy, Re2RegexStrategy>;
#else
using RegexStrategyTypes = testing::Types<StdRegexStrategy>;
#endif
TYPED_TEST_SUITE(TestRegexStrategy, RegexStrategyTypes);

TYPED_TEST(TestRegexStrategy, ReturnsNulloptWhenNoMatch)
{
TypeParam strategy{ R"__(hello)__" };

EXPECT_THAT(strategy.Match("world"), testing::IsFalse());
}

TYPED_TEST(TestRegexStrategy, ReturnsVectorWhenPatternMatches)
{
TypeParam strategy{ R"__((\d+))__" };

const auto result = strategy.Match("abc 42 def");

ASSERT_THAT(result, testing::IsTrue());
EXPECT_THAT(result->at(0), testing::StrEq("42")); // whole match
EXPECT_THAT(result->at(1), testing::StrEq("42")); // first capture group
}

TYPED_TEST(TestRegexStrategy, WholeMatchIsAtIndexZero)
{
TypeParam strategy{ R"__((\w+)\s+(\w+))__" };

const auto result = strategy.Match("hello world");

ASSERT_THAT(result, testing::IsTrue());
ASSERT_THAT(result->size(), testing::Eq(3));
EXPECT_THAT(result->at(0), testing::StrEq("hello world")); // whole match
EXPECT_THAT(result->at(1), testing::StrEq("hello")); // first capture group
EXPECT_THAT(result->at(2), testing::StrEq("world")); // second capture group
}

TYPED_TEST(TestRegexStrategy, MatchesNegativeInteger)
{
TypeParam strategy{ R"__((-?\d+))__" };

const auto result = strategy.Match("-22");

ASSERT_THAT(result, testing::IsTrue());
EXPECT_THAT(result->at(1), testing::StrEq("-22"));
}

TYPED_TEST(TestRegexStrategy, MatchesEmptyCapture)
{
TypeParam strategy{ R"__(^The value equals "([^"]*)"$)__" };

const auto result = strategy.Match(R"__(The value equals "")__");

ASSERT_THAT(result, testing::IsTrue());
EXPECT_THAT(result->at(1), testing::StrEq(""));
}

#ifdef CCR_HAS_RE2
TEST(Re2RegexStrategy, ThrowsOnInvalidPattern)
{
EXPECT_THROW(Re2RegexStrategy{ R"__([invalid)__" }, std::invalid_argument);
}
#endif
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MegaLinter] reported by reviewdog 🐶

Suggested change

Loading
Loading