Skip to content

Commit

Permalink
feat: enhance detail::stringify_full_match
Browse files Browse the repository at this point in the history
  • Loading branch information
DNKpp committed Jan 31, 2025
1 parent fab1578 commit 3b888f3
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 8 deletions.
51 changes: 43 additions & 8 deletions include/mimic++/reporting/StringifyReports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,39 @@ namespace mimicpp::reporting::detail
return std::move(ss).str();
}

struct inapplicable_reason_printer
{
template <print_iterator OutIter>
OutIter operator()([[maybe_unused]] OutIter out, [[maybe_unused]] state_applicable const& state) const
{
unreachable();
}

template <print_iterator OutIter>
OutIter operator()(OutIter out, const state_inapplicable& state) const
{
auto const totalSequences = std::ranges::ssize(state.sequenceRatings)
+ std::ranges::ssize(state.inapplicableSequences);
return format::format_to(
std::move(out),
"it's not head of {} Sequence(s) ({} total).",
std::ranges::ssize(state.inapplicableSequences),
totalSequences);
}

template <print_iterator OutIter>
OutIter operator()(OutIter out, const state_saturated& state) const
{
out = format::format_to(
std::move(out),
"it's already saturated (matched {} out of {} times).",
state.count,
state.max);

return out;
}
};

[[nodiscard]]
inline StringT stringify_inapplicable_matches(CallReport const& call, std::span<ExpectationReport> expectations)
{
Expand All @@ -160,14 +193,20 @@ namespace mimicpp::reporting::detail
stringify_call_report_arguments(std::ostreambuf_iterator{ss}, call, "\t\t");
}

ss << expectations.size() << " inapplicable but otherwise matching Expectation(s):\n";
ss << expectations.size() << " inapplicable but otherwise matching Expectation(s):";

for (int i{};
auto& expReport : expectations)
{
ss << "\t#" << ++i << " ";
ss << "\n\t#" << ++i << " ";
stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);

ss << "\tBecause ";
std::visit(
std::bind_front(inapplicable_reason_printer{}, std::ostreambuf_iterator{ss}),
expReport.controlReport);
ss << "\n";

if (!expReport.requirementDescriptions.empty())
{
ss << "\t" << "With Adherence(s):\n";
Expand All @@ -177,10 +216,6 @@ namespace mimicpp::reporting::detail
expReport.requirementDescriptions,
"\t + ");
}
else
{
ss << "\t" << "With any Requirements.\n";
}
}

stringify_stacktrace(
Expand Down Expand Up @@ -212,12 +247,12 @@ namespace mimicpp::reporting::detail
}
else
{
ss << noMatchReports.size() << " non-matching Expectation(s):\n";
ss << noMatchReports.size() << " non-matching Expectation(s):";

for (int i{};
auto& [expReport, outcomes] : noMatchReports)
{
ss << "\t#" << ++i << " ";
ss << "\n\t#" << ++i << " ";
stringify_expectation_report_from(std::ostreambuf_iterator{ss}, expReport);

ss << "\t" << "Due to Violation(s):\n";
Expand Down
164 changes: 164 additions & 0 deletions test/unit-tests/reporting/StringifyReports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ using namespace mimicpp;
namespace
{
inline reporting::control_state_t const commonApplicableState = reporting::state_applicable{13, 1337, 42};
inline reporting::control_state_t const commonInapplicableState = reporting::state_inapplicable{13, 1337, 42, {sequence::rating{1}}, {{sequence::Tag{1337}}}};
inline reporting::control_state_t const commonSaturatedState = reporting::state_saturated{42, 42, 42};

[[nodiscard]]
Stacktrace make_shallow_stacktrace()
{
Expand Down Expand Up @@ -170,3 +173,164 @@ TEST_CASE(
}

#endif

TEST_CASE(
"detail::stringify_inapplicable_matches converts the information to a pretty formatted text.",
"[reporting][detail]")
{
reporting::CallReport const callReport{
.returnTypeInfo = reporting::TypeReport::make<void>(),
.argDetails = {
{{reporting::TypeReport::make<int>(), "1337"},
{reporting::TypeReport::make<std::string>(), "\"Hello, World!\""}}},
.fromLoc = std::source_location::current(),
.fromCategory = ValueCategory::any,
.fromConstness = Constness::any};

reporting::ExpectationReport const expectationReport1{
.info = {.sourceLocation = std::source_location::current(), .mockName = "Mock-Name"},
.controlReport = commonInapplicableState,
.finalizerDescription = std::nullopt,
.requirementDescriptions = {
{"expect: arg[1] not empty",
std::nullopt,
"expect: arg[0] > 0"}}
};

reporting::ExpectationReport const expectationReport2{
.info = {.sourceLocation = std::source_location::current(), .mockName = "Mock-Name2"},
.controlReport = commonSaturatedState,
.finalizerDescription = std::nullopt,
.requirementDescriptions = {{"expect: test"}}
};

std::vector expectationReports{expectationReport1, expectationReport2};
auto const text = reporting::detail::stringify_inapplicable_matches(callReport, expectationReports);

// note the Adherence reordering
std::string const regex =
R"(Unmatched Call from `.+`#L\d+, `.+`
Where:
arg\[0\] => int: 1337
arg\[1\] => std::string: "Hello, World!"
2 inapplicable but otherwise matching Expectation\(s\):
#1 Expectation from `.+`#L\d+, `.+`
Because it's not head of 1 Sequence\(s\) \(2 total\).
With Adherence\(s\):
\+ expect: arg\[0\] > 0
\+ expect: arg\[1\] not empty
#2 Expectation from `.+`#L\d+, `.+`
Because it's already saturated \(matched 42 out of 42 times\).
With Adherence\(s\):
\+ expect: test
)";
REQUIRE_THAT(
text,
Catch::Matchers::Matches(regex));
}

TEST_CASE(
"detail::stringify_inapplicable_matches omits \"Where\"-Section, when no arguments exist.",
"[reporting][detail]")
{
reporting::CallReport const callReport{
.returnTypeInfo = reporting::TypeReport::make<void>(),
.argDetails = {},
.fromLoc = std::source_location::current(),
.fromCategory = ValueCategory::any,
.fromConstness = Constness::any};

reporting::ExpectationReport const expectationReport{
.info = {.sourceLocation = std::source_location::current(), .mockName = "Mock-Name"},
.controlReport = commonSaturatedState,
.finalizerDescription = std::nullopt,
.requirementDescriptions = {{"expect: some requirement"}}
};

std::vector expectationReports{expectationReport};
auto const text = reporting::detail::stringify_inapplicable_matches(callReport, expectationReports);

std::string const regex =
R"(Unmatched Call from `.+`#L\d+, `.+`
1 inapplicable but otherwise matching Expectation\(s\):
#1 Expectation from `.+`#L\d+, `.+`
Because it's already saturated \(matched 42 out of 42 times\).
With Adherence\(s\):
\+ expect: some requirement
)";
REQUIRE_THAT(
text,
Catch::Matchers::Matches(regex));
}

TEST_CASE(
"detail::stringify_inapplicable_matches omits \"With Adherence(s)\"-Section, when no requirements exist.",
"[reporting][detail]")
{
reporting::CallReport const callReport{
.returnTypeInfo = reporting::TypeReport::make<void>(),
.argDetails = {},
.fromLoc = std::source_location::current(),
.fromCategory = ValueCategory::any,
.fromConstness = Constness::any};

reporting::ExpectationReport const expectationReport{
.info = {.sourceLocation = std::source_location::current(), .mockName = "Mock-Name"},
.controlReport = commonSaturatedState,
.finalizerDescription = std::nullopt
};

std::vector expectationReports{expectationReport};
auto const text = reporting::detail::stringify_inapplicable_matches(callReport, expectationReports);

std::string const regex =
R"(Unmatched Call from `.+`#L\d+, `.+`
1 inapplicable but otherwise matching Expectation\(s\):
#1 Expectation from `.+`#L\d+, `.+`
Because it's already saturated \(matched 42 out of 42 times\).
)";
REQUIRE_THAT(
text,
Catch::Matchers::Matches(regex));
}

#if MIMICPP_DETAIL_HAS_WORKING_STACKTRACE_BACKEND

TEST_CASE(
"detail::stringify_inapplicable_matches adds the Stacktrace, if existing.",
"[reporting][detail]")
{
reporting::CallReport const callReport{
.returnTypeInfo = reporting::TypeReport::make<void>(),
.argDetails = {},
.fromLoc = std::source_location::current(),
.stacktrace = make_shallow_stacktrace(),
.fromCategory = ValueCategory::any,
.fromConstness = Constness::any};

reporting::ExpectationReport const expectationReport{
.info = {.sourceLocation = std::source_location::current(), .mockName = "Mock-Name"},
.controlReport = commonInapplicableState,
.finalizerDescription = std::nullopt
};

std::vector expectationReports{expectationReport};
auto const text = reporting::detail::stringify_inapplicable_matches(callReport, expectationReports);
CAPTURE(text);
auto const stacktraceBegin = std::ranges::search(text, std::string_view{"Stacktrace:\n"}).begin();
REQUIRE(stacktraceBegin != text.cend());
REQUIRE_THAT(
(std::string{text.cbegin(), stacktraceBegin}),
Catch::Matchers::EndsWith("Because it's not head of 1 Sequence(s) (2 total).\n\n"));

std::string const stacktraceRegex =
R"(Stacktrace:
#0 `.+`#L\d+, `.+`
(?:#\d+ `.*`#L\d+, `.*`\n)*)";
REQUIRE_THAT(
(std::string{stacktraceBegin, text.cend()}),
Catch::Matchers::Matches(stacktraceRegex));
}

#endif

0 comments on commit 3b888f3

Please sign in to comment.