Skip to content

Commit

Permalink
feat: add custom report handler (#32)
Browse files Browse the repository at this point in the history
* feat: add custom report handler

Fixes #28
Fixes #29

* chore: made TableValue:As const

* chore: fixed TestSteps

* chore: added ambiguous step test

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings

* chore: resolved sonar warnings
  • Loading branch information
daantimmer authored Jan 24, 2024
1 parent fd8eae6 commit 051764f
Show file tree
Hide file tree
Showing 40 changed files with 1,352 additions and 719 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"twxs.cmake",
"ms-vscode.cpptools",
"akiramiyakoda.cppincludeguard",
"alexkrechik.cucumberautocomplete"
"alexkrechik.cucumberautocomplete",
"SonarSource.sonarlint-vscode"
]
}
}
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
"C/C++ Include Guard.Comment Style": "None",
"C/C++ Include Guard.Path Skip": 0,
"testMate.cpp.test.executables": "${workspaceFolder}/.build/**/*{test,Test,TEST}*",
"sonarlint.pathToCompileCommands": "${workspaceFolder}/.build/compile_commands.json",
}
1 change: 0 additions & 1 deletion cucumber-cpp-runner/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ int main(int argc, char** argv)
auto args = application.GetForwardArgs();

application.RunFeatures();
application.GenerateReports();

return application.GetExitCode();
}
102 changes: 43 additions & 59 deletions cucumber-cpp/Application.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#include "cucumber-cpp/Application.hpp"
#include "cucumber-cpp/Context.hpp"
#include "cucumber-cpp/CucumberRunner.hpp"
#include "cucumber-cpp/ResultStates.hpp"
#include "cucumber-cpp/report/JsonReport.hpp"
#include "cucumber-cpp/report/JunitReport.hpp"
#include "cucumber-cpp/report/Report.hpp"
#include "cucumber-cpp/report/StdOutReport.hpp"
#include "cucumber/gherkin/file.hpp"
#include "cucumber/gherkin/parse_error.hpp"
#include <algorithm>
#include <cstdlib>
#include <filesystem>
Expand Down Expand Up @@ -74,44 +74,34 @@ namespace cucumber_cpp
{
const auto arg = *current;
if (arg == "--tag")
{
while (std::next(current) != view.end() && (!(*std::next(current)).starts_with("-")))
{
current = std::next(current);
tags.push_back(*current);
}
}
else if (arg == "--feature")
{
while (std::next(current) != view.end() && (!(*std::next(current)).starts_with("-")))
{
current = std::next(current);
features.push_back(*current);
}
}
else if (arg == "--report")
{
while (std::next(current) != view.end() && (!(*std::next(current)).starts_with("-")))
{
current = std::next(current);
reports.push_back(*current);
}
}
else if (arg.starts_with("--Xapp,"))
{
const auto param = arg.substr(std::string_view("--Xapp,").size());

for (const auto xArg : std::views::split(param, ','))
{
forwardArgs.push_back(subrange_to_sv(xArg));
}
}
else
{
if (!(arg == "--help" && arg == "-h"))
{
std::cout << "\nUnkown argument: " << std::quoted(arg) << "\n";
}

ExitWithHelp(name);
}
Expand Down Expand Up @@ -152,86 +142,80 @@ namespace cucumber_cpp
validateArguments();
}

Application::Application(std::span<const char*> args)
: options{ args }
GherkinParser::GherkinParser(CucumberRunnerV2& cucumberRunner)
: cucumberRunner{ cucumberRunner }
, cbs{
.ast = [&](const cucumber::gherkin::app::parser_result& ast)
{
nlohmann::json astJson;

ast.to_json(astJson);

root["features"].push_back({ { "ast", astJson } });
featureRunner = cucumberRunner.StartFeature(ast);
},
.pickle = [&](const cucumber::messages::pickle& pickle)
{
nlohmann::json scenarioJson;

pickle.to_json(scenarioJson);

root["features"].back()["scenarios"].push_back(scenarioJson);
featureRunner->StartScenario(pickle);
},
.error = [&](const auto& m)
{
std::cout << m.to_json() << std::endl;
}
.error = [&](const cucumber::gherkin::parse_error& m) {}
}
{
root["tagexpression"] = options.tags;
app.include_source(false);
app.include_ast(true);
app.include_pickles(true);
}

report::ReportHandler::Result GherkinParser::RunFeatureFile(const std::filesystem::path& path)
{
app.parse(cucumber::gherkin::file{ path.string() }, cbs);
auto result = featureRunner->Result();
featureRunner = nullptr;
return result;
}

Application::Application(std::span<const char*> args)
: options{ args }
{
if (std::ranges::find(options.reports, "console") != options.reports.end())
reporters.Add(std::make_unique<report::StdOutReportV2>());

if (std::ranges::find(options.reports, "junit-xml") != options.reports.end())
reporters.Add(std::make_unique<report::JunitReportV2>());
}

const std::vector<std::string_view>& Application::GetForwardArgs() const
{
return options.forwardArgs;
}

void Application::RunFeatures(std::shared_ptr<ContextStorageFactory> contextStorageFactory)
{
for (const auto& featurePath : GetFeatureFiles())
app.parse(cucumber::gherkin::file{ featurePath.string() }, cbs);

const auto tagExpression = options.tags.empty() ? std::string{} : std::accumulate(std::next(options.tags.begin()), options.tags.end(), std::string(options.tags.front()), JoinStringWithSpace);
using Result = report::ReportHandler::Result;

CucumberRunner cucumberRunner{ GetForwardArgs(), tagExpression, std::move(contextStorageFactory) };
cucumberRunner.Run(root);
auto tagExpression = options.tags.empty() ? std::string{} : std::accumulate(std::next(options.tags.begin()), options.tags.end(), std::string(options.tags.front()), JoinStringWithSpace);

if (!root.contains("result"))
{
std::cout << "\nError: no features have been executed";
}
}
CucumberRunnerV2 cucumberRunner{ GetForwardArgs(), std::move(tagExpression), reporters, std::move(contextStorageFactory) };
GherkinParser gherkinParser{ cucumberRunner };

void Application::GenerateReports(const std::map<std::string_view, report::Report&>& /*unused*/)
{
if (std::ranges::find(options.reports, "json") != options.reports.end())
for (const auto& featurePath : GetFeatureFiles())
{
cucumber_cpp::report::JsonReport report;
report.GenerateReport(root);
}
auto featureResult = gherkinParser.RunFeatureFile(featurePath);

if (std::ranges::find(options.reports, "console") != options.reports.end())
{
cucumber_cpp::report::StdOutReport report;
report.GenerateReport(root);
if (result == Result::undefined || result == Result::success)
result = featureResult;
}

if (std::ranges::find(options.reports, "junit-xml") != options.reports.end())
{
cucumber_cpp::report::JunitReport junitReport;
junitReport.GenerateReport(root);
}
if (result == Result::undefined)
std::cout << "\nError: no features have been executed";
}

int Application::GetExitCode() const
{
if (root.contains("result") && root["result"] == result::success)
{
if (result == decltype(result)::success)
return 0;
}
return 1;
else
return 1;
}

[[nodiscard]] report::Reporters& Application::Reporters()
{
return reporters;
}

std::vector<std::filesystem::path> Application::GetFeatureFiles() const
Expand Down
27 changes: 23 additions & 4 deletions cucumber-cpp/Application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,47 @@
#define CUCUMBER_CPP_APPLICATION_HPP

#include "cucumber-cpp/Context.hpp"
#include "cucumber-cpp/CucumberRunner.hpp"
#include "cucumber-cpp/FeatureRunner.hpp"
#include "cucumber-cpp/report/Report.hpp"
#include "cucumber/gherkin/app.hpp"
#include <filesystem>
#include <map>
#include <memory>
#include <optional>
#include <span>
#include <string_view>
#include <utility>
#include <vector>

namespace cucumber_cpp
{
struct GherkinParser
{
explicit GherkinParser(CucumberRunnerV2& cucumberRunner);

[[nodiscard]] report::ReportHandler::Result RunFeatureFile(const std::filesystem::path& path);

private:
CucumberRunnerV2& cucumberRunner;
std::unique_ptr<FeatureRunnerV2> featureRunner;

cucumber::gherkin::app app;
cucumber::gherkin::app::callbacks cbs;
};

struct Application
{
Application(std::span<const char*> args);

[[nodiscard]] const std::vector<std::string_view>& GetForwardArgs() const;

void RunFeatures(std::shared_ptr<ContextStorageFactory> contextStorageFactory = std::make_shared<ContextStorageFactoryImpl>());
void GenerateReports(const std::map<std::string_view, report::Report&>& additionalReports = {});

[[nodiscard]] int GetExitCode() const;

[[nodiscard]] report::Reporters& Reporters();

private:
[[nodiscard]] std::vector<std::filesystem::path> GetFeatureFiles() const;

Expand All @@ -37,10 +57,9 @@ namespace cucumber_cpp
};

Options options;
nlohmann::json root;

cucumber::gherkin::app app;
cucumber::gherkin::app::callbacks cbs;
report::ReportForwarder reporters;
report::ReportHandler::Result result{ report::ReportHandler::Result::undefined };
};
}

Expand Down
15 changes: 9 additions & 6 deletions cucumber-cpp/Body.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#ifndef CUCUMBER_CPP_BODY_HPP
#define CUCUMBER_CPP_BODY_HPP

#include "nlohmann/json.hpp"
#include <cstddef>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

namespace cucumber_cpp
{
Expand All @@ -31,19 +34,19 @@ namespace cucumber_cpp
{
virtual ~Body() = default;

virtual void Execute(const nlohmann::json& parameters = {}) = 0;
virtual void Execute(const std::vector<std::string>& args = {}) = 0;

protected:
template<class T, class... Args, std::size_t... I>
void InvokeWithArgImpl(T* t, const nlohmann::json& json, void (T::*ptr)(Args...), std::index_sequence<I...>)
void InvokeWithArgImpl(T* t, const std::vector<std::string>& args, void (T::*ptr)(Args...), std::index_sequence<I...>) const
{
(t->*ptr)(StringTo<std::remove_cvref_t<Args>>(json[I])...);
(t->*ptr)(StringTo<std::remove_cvref_t<Args>>(args[I])...);
}

template<class T, class... Args>
void InvokeWithArg(T* t, const nlohmann::json& json, void (T::*ptr)(Args...))
void InvokeWithArg(T* t, const std::vector<std::string>& args, void (T::*ptr)(Args...)) const
{
InvokeWithArgImpl(t, json, ptr, std::make_index_sequence<sizeof...(Args)>{});
InvokeWithArgImpl(t, args, ptr, std::make_index_sequence<sizeof...(Args)>{});
}
};
}
Expand Down
10 changes: 5 additions & 5 deletions cucumber-cpp/BodyMacro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@

#define BODY_STRUCT CONCAT(BodyImpl, __LINE__)

#define BODY(matcher, type, args, registration, base) \
#define BODY(matcher, type, targs, registration, base) \
namespace \
{ \
struct BODY_STRUCT : cucumber_cpp::Body \
, base \
{ \
using myBase = base; \
using myBase::myBase; \
void Execute(const nlohmann::json& parameters = {}) override \
void Execute(const std::vector<std::string>& args) override \
{ \
InvokeWithArg(this, parameters, &BODY_STRUCT::ExecuteWithArgs); \
InvokeWithArg(this, args, &BODY_STRUCT::ExecuteWithArgs); \
} \
\
private: \
void ExecuteWithArgs args; \
void ExecuteWithArgs targs; \
static const std::size_t ID; \
}; \
} \
const std::size_t BODY_STRUCT::ID = cucumber_cpp::registration<BODY_STRUCT>(matcher, type); \
void BODY_STRUCT::ExecuteWithArgs args
void BODY_STRUCT::ExecuteWithArgs targs

#endif
4 changes: 1 addition & 3 deletions cucumber-cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ target_sources(cucumber-cpp PRIVATE
Hooks.hpp
HookScopes.cpp
HookScopes.hpp
JsonTagToSet.cpp
JsonTagToSet.hpp
OnTestPartResultEventListener.cpp
OnTestPartResultEventListener.hpp
Rtrim.cpp
Expand All @@ -32,6 +30,7 @@ target_sources(cucumber-cpp PRIVATE
StepRunner.hpp
TagExpression.cpp
TagExpression.hpp
TagsToSet.hpp
TraceTime.cpp
TraceTime.hpp
)
Expand All @@ -43,7 +42,6 @@ target_include_directories(cucumber-cpp PUBLIC
target_link_libraries(cucumber-cpp PUBLIC
gtest
cucumber_gherkin_lib
nlohmann_json::nlohmann_json

PRIVATE
cucumber-cpp.report
Expand Down
Loading

0 comments on commit 051764f

Please sign in to comment.