From d02d1c9ef05c4a5bb8c4e99988e544cd5653e8fe Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 2 May 2024 22:20:05 +0200 Subject: [PATCH 01/68] feat: add Fwd.hpp --- include/mimic++/Call.hpp | 21 +--------------- include/mimic++/Fwd.hpp | 50 +++++++++++++++++++++++++++++++++++++ include/mimic++/Printer.hpp | 5 ++-- 3 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 include/mimic++/Fwd.hpp diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index bc80eb81f..0aa04ea0e 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -8,6 +8,7 @@ #pragma once +#include "mimic++/Fwd.hpp" #include "mimic++/TypeTraits.hpp" #include "mimic++/Utility.hpp" @@ -79,16 +80,6 @@ namespace mimicpp::call }; } -namespace mimicpp::call -{ - enum class MatchCategory - { - no, - non_applicable, - ok - }; -} - template struct std::formatter : public std::formatter, Char> @@ -140,16 +131,6 @@ namespace mimicpp::call [[nodiscard]] friend bool operator ==(const GenericMatchResult&, const GenericMatchResult&) = default; }; - - using MatchResult_NoT = GenericMatchResult; - using MatchResult_NotApplicableT = GenericMatchResult; - using MatchResult_OkT = GenericMatchResult; - - using MatchResultT = std::variant< - MatchResult_NoT, - MatchResult_NotApplicableT, - MatchResult_OkT - >; } namespace mimicpp::call::detail diff --git a/include/mimic++/Fwd.hpp b/include/mimic++/Fwd.hpp new file mode 100644 index 000000000..9308c3048 --- /dev/null +++ b/include/mimic++/Fwd.hpp @@ -0,0 +1,50 @@ +// // Copyright Dominic (DNKpp) Koepke 2024 - 2024. +// // Distributed under the Boost Software License, Version 1.0. +// // (See accompanying file LICENSE_1_0.txt or copy at +// // https://www.boost.org/LICENSE_1_0.txt) + +#ifndef MIMICPP_FWD_HPP +#define MIMICPP_FWD_HPP + +#pragma once + +#include +#include + +namespace mimicpp::call +{ + enum class MatchCategory + { + no, + non_applicable, + ok + }; + + template + class GenericMatchResult; + + using MatchResult_NoT = GenericMatchResult; + using MatchResult_NotApplicableT = GenericMatchResult; + using MatchResult_OkT = GenericMatchResult; + + using MatchResultT = std::variant< + MatchResult_NoT, + MatchResult_NotApplicableT, + MatchResult_OkT + >; + + template + class Info; +} + +namespace mimicpp +{ + template + class Expectation; + + using CharT = char; + using CharTraitsT = std::char_traits; + using StringT = std::basic_string; +} + +#endif diff --git a/include/mimic++/Printer.hpp b/include/mimic++/Printer.hpp index d62c515aa..1690241e4 100644 --- a/include/mimic++/Printer.hpp +++ b/include/mimic++/Printer.hpp @@ -8,6 +8,8 @@ #pragma once +#include "Fwd.hpp" + #include #include #include @@ -17,9 +19,6 @@ namespace mimicpp { - using CharT = char; - using CharTraitsT = std::char_traits; - using StringT = std::basic_string; using StringViewT = std::basic_string_view; using StringStreamT = std::basic_ostringstream; From e58e9b42af8f53108c0a02e2c1314aa857c1e260 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Thu, 2 May 2024 22:23:21 +0200 Subject: [PATCH 02/68] feat: make Reporter compile time exchangable --- include/mimic++/Reporter.hpp | 124 +++++++++++++++++++++++-- test/Expectation.cpp | 3 +- test/ExpectationBuilder.cpp | 25 +++-- test/ExpectationPolicies.cpp | 4 +- test/Mock.cpp | 4 +- test/Sequence.cpp | 6 +- test/TestReporter.hpp | 175 +++++++++++++++++++++-------------- 7 files changed, 250 insertions(+), 91 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 853d88fcd..69c52adef 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -9,6 +9,7 @@ #pragma once #include "mimic++/Call.hpp" +#include "mimic++/Fwd.hpp" #include "mimic++/Printer.hpp" #include @@ -17,42 +18,147 @@ namespace mimicpp { + template + class Expectation; + + class DefaultReporter + { + public: + template + [[noreturn]] + static void report_fail( + const call::Info& callInfo, + const std::vector& results + ) + { + std::terminate(); + } + + template + [[noreturn]] + static void report_fail( + const call::Info& callInfo, + const std::vector& results + ) + { + std::terminate(); + } + + template + static constexpr void report_ok( + const call::Info& callInfo, + const call::MatchResult_OkT& result + ) + { + } + + static void report_error(const StringT& message) + { + if (0 != std::uncaught_exceptions()) + { + std::terminate(); + } + } + + template + static constexpr void report_unhandled_exception( + const call::Info& callInfo, + std::shared_ptr> expectation, + std::exception_ptr exception + ) + { + } + + template + static constexpr void report_unsatisfied_expectation( + std::shared_ptr> expectation + ) + { + if (0 != std::uncaught_exceptions()) + { + std::terminate(); + } + } + }; + + [[nodiscard]] + constexpr auto create_reporter() noexcept + { +#ifdef MIMICPP_CUSTOM_REPORTER + return MIMICPP_CUSTOM_REPORTER{}; +#else + return DefaultReporter{}; +#endif + } + template [[noreturn]] void report_fail( const call::Info& callInfo, std::vector results - ); + ) + { + auto reporter = create_reporter(); + reporter.report_fail( + callInfo, + std::move(results)); + } template [[noreturn]] void report_fail( const call::Info& callInfo, std::vector results - ); + ) + { + auto reporter = create_reporter(); + reporter.report_fail( + callInfo, + std::move(results)); + } template void report_ok( const call::Info& callInfo, call::MatchResult_OkT result - ); + ) + { + auto reporter = create_reporter(); + reporter.report_ok( + callInfo, + std::move(result)); + } - inline void report_error(StringT message); - - template - class Expectation; + inline void report_error(StringT message) + { + auto reporter = create_reporter(); + reporter.report_error( + std::move(message)); + } template void report_unhandled_exception( const call::Info& callInfo, std::shared_ptr> expectation, std::exception_ptr exception - ); + ) + { + auto reporter = create_reporter(); + reporter.report_unhandled_exception( + callInfo, + std::move(expectation), + std::move(exception)); + } template void report_unsatisfied_expectation( std::shared_ptr> expectation - ); + ) + { + auto reporter = create_reporter(); + reporter.report_unsatisfied_expectation( + std::move(expectation)); + } } #endif diff --git a/test/Expectation.cpp b/test/Expectation.cpp index c93ec81a7..df1e3c737 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -3,10 +3,11 @@ // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) +#include "TestReporter.hpp" + #include "mimic++/Expectation.hpp" #include "mimic++/Printer.hpp" -#include "TestReporter.hpp" #include "TestTypes.hpp" #include diff --git a/test/ExpectationBuilder.cpp b/test/ExpectationBuilder.cpp index 13fbcea36..7f01b3d49 100644 --- a/test/ExpectationBuilder.cpp +++ b/test/ExpectationBuilder.cpp @@ -1,5 +1,12 @@ -#include "mimic++/ExpectationBuilder.hpp" +// // Copyright Dominic (DNKpp) Koepke 2024 - 2024. +// // Distributed under the Boost Software License, Version 1.0. +// // (See accompanying file LICENSE_1_0.txt or copy at +// // https://www.boost.org/LICENSE_1_0.txt) + #include "TestReporter.hpp" + +#include "mimic++/ExpectationBuilder.hpp" + #include "TestTypes.hpp" #include @@ -277,16 +284,16 @@ TEST_CASE( const auto collection = std::make_shared>(); MIMICPP_SCOPED_EXPECTATION BaseBuilderT{ - collection, - TimesFake{.isSatisfied = true}, - expectation_policies::InitFinalize{}, - std::tuple{}}; + collection, + TimesFake{.isSatisfied = true}, + expectation_policies::InitFinalize{}, + std::tuple{}}; MIMICPP_SCOPED_EXPECTATION BaseBuilderT{ - collection, - TimesFake{.isSatisfied = true}, - expectation_policies::InitFinalize{}, - std::tuple{}}; + collection, + TimesFake{.isSatisfied = true}, + expectation_policies::InitFinalize{}, + std::tuple{}}; } REQUIRE_THAT( diff --git a/test/ExpectationPolicies.cpp b/test/ExpectationPolicies.cpp index 8db7249ee..394c91c61 100644 --- a/test/ExpectationPolicies.cpp +++ b/test/ExpectationPolicies.cpp @@ -3,8 +3,10 @@ // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) -#include "mimic++/ExpectationPolicies.hpp" #include "TestReporter.hpp" + +#include "mimic++/ExpectationPolicies.hpp" + #include "TestTypes.hpp" #include diff --git a/test/Mock.cpp b/test/Mock.cpp index 543db71a3..0b0891a42 100644 --- a/test/Mock.cpp +++ b/test/Mock.cpp @@ -3,8 +3,10 @@ // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) -#include "mimic++/Mock.hpp" #include "TestReporter.hpp" + +#include "mimic++/Mock.hpp" + #include "TestTypes.hpp" #include diff --git a/test/Sequence.cpp b/test/Sequence.cpp index 6ebbe5a9c..b933d9e73 100644 --- a/test/Sequence.cpp +++ b/test/Sequence.cpp @@ -3,6 +3,8 @@ // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) +#include "TestReporter.hpp" + #include "mimic++/Sequence.hpp" #include "mimic++/Expectation.hpp" @@ -13,8 +15,6 @@ #include #include -#include "TestReporter.hpp" - using namespace mimicpp; TEST_CASE( @@ -318,7 +318,7 @@ TEST_CASE( REQUIRE(policy2.is_satisfied()); REQUIRE(!policy2.is_applicable()); } - + SECTION("When an expectation waits for multiple sequences.") { PolicyT policy1 = expect::in_sequences({sequence1}); diff --git a/test/TestReporter.hpp b/test/TestReporter.hpp index 059693274..8962a8f0b 100644 --- a/test/TestReporter.hpp +++ b/test/TestReporter.hpp @@ -5,12 +5,62 @@ #pragma once -#include "mimic++/Reporter.hpp" +#include "mimic++/Fwd.hpp" #include +#include #include +#include + +class NoMatchError +{ +}; -#define MIMICPP_REPORTER_DEFINED +class NonApplicableMatchError +{ +}; + +class TestReporter +{ +public: + template + [[noreturn]] + static void report_fail( + const mimicpp::call::Info& callInfo, + const std::vector& results + ); + + template + [[noreturn]] + static void report_fail( + const mimicpp::call::Info& callInfo, + const std::vector& results + ); + + template + static constexpr void report_ok( + const mimicpp::call::Info& callInfo, + const mimicpp::call::MatchResult_OkT& result + ); + + static void report_error(const mimicpp::StringT& message); + + template + static constexpr void report_unhandled_exception( + const mimicpp::call::Info& callInfo, + std::shared_ptr> expectation, + std::exception_ptr exception + ); + + template + static constexpr void report_unsatisfied_expectation( + std::shared_ptr> expectation + ); +}; + +#define MIMICPP_CUSTOM_REPORTER TestReporter + +#include "mimic++/Reporter.hpp" inline std::vector g_NoMatchResults{}; inline std::vector g_NonApplicableMatchResults{}; @@ -22,85 +72,76 @@ struct unhandled_exception_info std::any expectation{}; std::exception_ptr exception{}; }; + inline std::vector g_UnhandledExceptions{}; inline std::vector g_UnsatisfiedExpectations{}; inline std::vector g_Errors{}; -class NoMatchError +template +void TestReporter::report_fail( + const mimicpp::call::Info& callInfo, + const std::vector& results +) { -}; + g_NoMatchResults.insert( + std::ranges::end(g_NoMatchResults), + std::ranges::begin(results), + std::ranges::end(results)); -class NonApplicableMatchError -{ -}; + throw NoMatchError{}; +} -namespace mimicpp +template +void TestReporter::report_fail( + const mimicpp::call::Info& callInfo, + const std::vector& results +) { - template - void report_fail( - const call::Info& callInfo, - std::vector results - ) - { - g_NoMatchResults.insert( - std::ranges::end(g_NoMatchResults), - std::ranges::begin(results), - std::ranges::end(results)); + g_NonApplicableMatchResults.insert( + std::ranges::end(g_NonApplicableMatchResults), + std::ranges::begin(results), + std::ranges::end(results)); - throw NoMatchError{}; - } - - template - void report_fail( - const call::Info& callInfo, - std::vector results - ) - { - g_NonApplicableMatchResults.insert( - std::ranges::end(g_NonApplicableMatchResults), - std::ranges::begin(results), - std::ranges::end(results)); - - throw NonApplicableMatchError{}; - } + throw NonApplicableMatchError{}; +} - template - void report_ok( - const call::Info& callInfo, - call::MatchResult_OkT result - ) - { - g_OkMatchResults.emplace_back(std::move(result)); - } +template +constexpr void TestReporter::report_ok( + const mimicpp::call::Info& callInfo, + const mimicpp::call::MatchResult_OkT& result +) +{ + g_OkMatchResults.emplace_back(std::move(result)); +} - void report_error(StringT message) - { - g_Errors.emplace_back(std::move(message)); - } +inline void TestReporter::report_error(const mimicpp::StringT& message) +{ + g_Errors.emplace_back(std::move(message)); +} - template - void report_unhandled_exception( - const call::Info& callInfo, - std::shared_ptr> expectation, - std::exception_ptr exception - ) - { - g_UnhandledExceptions.emplace_back( - unhandled_exception_info{ - .call = callInfo, - .expectation = std::move(expectation), - .exception = exception - }); - } +template +constexpr void TestReporter::report_unhandled_exception( + const mimicpp::call::Info& callInfo, + std::shared_ptr> expectation, + std::exception_ptr exception +) +{ + g_UnhandledExceptions.emplace_back( + unhandled_exception_info{ + .call = callInfo, + .expectation = std::move(expectation), + .exception = exception + }); +} - template - void report_unsatisfied_expectation( - std::shared_ptr> expectation - ) - { - g_UnsatisfiedExpectations.emplace_back(std::move(expectation)); - } +template +constexpr void TestReporter::report_unsatisfied_expectation(std::shared_ptr> expectation) +{ + g_UnsatisfiedExpectations.emplace_back(std::move(expectation)); +} +namespace mimicpp +{ class ScopedReporter { public: From c36d268da9a7ffc8912f29c69ebaabaa692acb3b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 00:05:41 +0200 Subject: [PATCH 03/68] refactor: matcher description --- examples/Requirements.cpp | 4 +- include/mimic++/ExpectationPolicies.hpp | 2 +- include/mimic++/Matcher.hpp | 77 ++++-- test/ExpectationPolicies.cpp | 12 +- test/Matcher.cpp | 353 ++++++++++-------------- test/TestTypes.hpp | 7 +- 6 files changed, 204 insertions(+), 251 deletions(-) diff --git a/examples/Requirements.cpp b/examples/Requirements.cpp index 806d88a94..8a3306221 100644 --- a/examples/Requirements.cpp +++ b/examples/Requirements.cpp @@ -161,7 +161,9 @@ TEST_CASE( return std::ranges::find(target, element) != std::ranges::end(target); }, // specify a descriptive format message, which will be applied to std::format. - "range {} contains the element {}", + "contains element {}", + // specify the inverted message, which will also be applied to std::format, when inversion is used + "contains not element {}", // capture additional data, which will be forwarded to both, the predicate and the description std::tuple{42} }; diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index 7a17c7d32..54e6f8bf7 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -278,7 +278,7 @@ namespace mimicpp::expectation_policies .msg = std::invoke( m_Describer, target, - m_Matcher.describe(target), + m_Matcher.describe(), matchResult) }; } diff --git a/include/mimic++/Matcher.hpp b/include/mimic++/Matcher.hpp index 23328f59f..a1ad416cf 100644 --- a/include/mimic++/Matcher.hpp +++ b/include/mimic++/Matcher.hpp @@ -27,7 +27,7 @@ namespace mimicpp && requires(const T& matcher, Target& target) { { matcher.matches(target) } -> std::convertible_to; - { matcher.describe(target) } -> std::convertible_to; + { matcher.describe() } -> std::convertible_to; }; /** @@ -47,11 +47,13 @@ namespace mimicpp explicit constexpr PredicateMatcher( Predicate predicate, StringT fmt, + StringT invertedFmt, std::tuple additionalArgs = std::tuple{} ) noexcept(std::is_nothrow_move_constructible_v && (... && std::is_nothrow_move_constructible_v)) : m_Predicate{std::move(predicate)}, m_FormatString{std::move(fmt)}, + m_InvertedFormatString{std::move(invertedFmt)}, m_AdditionalArgs{std::move(additionalArgs)} { } @@ -74,9 +76,8 @@ namespace mimicpp m_AdditionalArgs); } - template [[nodiscard]] - constexpr StringT describe(T& target) const + constexpr StringT describe() const { return std::apply( [&, this](auto&... additionalArgs) @@ -86,7 +87,6 @@ namespace mimicpp return format::vformat( m_FormatString, format::make_format_args( - makeLvalue(mimicpp::print(target)), makeLvalue(mimicpp::print(additionalArgs))...)); }, m_AdditionalArgs); @@ -99,6 +99,7 @@ namespace mimicpp { return make_inverted( m_Predicate, + m_InvertedFormatString, m_FormatString, std::move(m_AdditionalArgs)); } @@ -108,23 +109,31 @@ namespace mimicpp { return make_inverted( std::move(m_Predicate), - m_FormatString, + std::move(m_InvertedFormatString), + std::move(m_FormatString), std::move(m_AdditionalArgs)); } private: [[no_unique_address]] Predicate m_Predicate; StringT m_FormatString; + StringT m_InvertedFormatString; mutable std::tuple m_AdditionalArgs{}; template [[nodiscard]] - static constexpr auto make_inverted(Fn&& fn, const StringViewT fmt, std::tuple tuple) + static constexpr auto make_inverted( + Fn&& fn, + StringT fmt, + StringT invertedFmt, + std::tuple tuple + ) { using NotFnT = decltype(std::not_fn(std::forward(fn))); return PredicateMatcher{ std::not_fn(std::forward(fn)), - format::format("not ({})", fmt), + std::move(fmt), + std::move(invertedFmt), std::move(tuple) }; } @@ -144,11 +153,9 @@ namespace mimicpp return true; } - static constexpr StringT describe(auto&& target) + static constexpr StringT describe() { - return format::format( - "{} without constraints", - mimicpp::print(target)); + return "has no constraints"; } }; } @@ -205,7 +212,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::equal_to{}, - "{} == {}", + "== {}", + "!= {}", std::tuple{std::forward(value)} }; } @@ -221,7 +229,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::not_equal_to{}, - "{} != {}", + "!= {}", + "== {}", std::tuple{std::forward(value)} }; } @@ -237,7 +246,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::less{}, - "{} < {}", + "< {}", + ">= {}", std::tuple{std::forward(value)} }; } @@ -253,7 +263,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::less_equal{}, - "{} <= {}", + "<= {}", + "> {}", std::tuple{std::forward(value)} }; } @@ -269,7 +280,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::greater{}, - "{} > {}", + "> {}", + "<= {}", std::tuple{std::forward(value)} }; } @@ -285,7 +297,8 @@ namespace mimicpp::matches { return PredicateMatcher{ std::ranges::greater_equal{}, - "{} >= {}", + ">= {}", + "< {}", std::tuple{std::forward(value)} }; } @@ -294,16 +307,22 @@ namespace mimicpp::matches * \brief Tests, whether the target fulfills the given predicate. * \tparam UnaryPredicate Predicate type. * \param predicate The predicate to test. - * \param description The formatting string. May contain a ``{}``-token for the target. + * \param description The formatting string. + * \param invertedDescription The formatting string for the inversion. * \snippet Requirements.cpp matcher predicate */ template [[nodiscard]] - constexpr auto predicate(UnaryPredicate&& predicate, StringT description = "{} satisfies predicate") + constexpr auto predicate( + UnaryPredicate&& predicate, + StringT description = "passes predicate", + StringT invertedDescription = "fails predicate" + ) { return PredicateMatcher{ std::forward(predicate), - std::move(description) + std::move(description), + std::move(invertedDescription), }; } @@ -340,7 +359,8 @@ namespace mimicpp::matches::str { return target == exp; }, - "string {} is equal to {}", + "is equal to {}", + "is not equal to {}", std::tuple{std::move(expected)} }; } @@ -398,7 +418,8 @@ namespace mimicpp::matches::range range, std::ref(comp)); }, - "range {} is equal to {}", + "elements are {}", + "elements are not {}", std::tuple{std::views::all(std::forward(expected))} }; } @@ -427,7 +448,8 @@ namespace mimicpp::matches::range range, std::ref(comp)); }, - "range {} is permutation of {}", + "is a permutation of {}", + "is not a permutation of {}", std::tuple{std::views::all(std::forward(expected))} }; } @@ -452,7 +474,8 @@ namespace mimicpp::matches::range target, std::ref(rel)); }, - "range {} is sorted" + "is a sorted range", + "is an unsorted range" }; } @@ -467,7 +490,8 @@ namespace mimicpp::matches::range { return std::ranges::empty(target); }, - "range {} is empty" + "is an empty range", + "is not an empty range" }; } @@ -485,7 +509,8 @@ namespace mimicpp::matches::range size, std::ranges::size(target)); }, - "range {} has size {}", + "has size of {}", + "has different size than {}", std::tuple{expected} }; } diff --git a/test/ExpectationPolicies.cpp b/test/ExpectationPolicies.cpp index 394c91c61..9054670d2 100644 --- a/test/ExpectationPolicies.cpp +++ b/test/ExpectationPolicies.cpp @@ -411,8 +411,7 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(true); - REQUIRE_CALL(matcher, describe(_)) - .LR_WITH(&_1 == &arg0) + REQUIRE_CALL(matcher, describe()) .RETURN("success"); REQUIRE_CALL(describer, Invoke(_, "success", true)) .LR_WITH(&_1 == &arg0) @@ -434,8 +433,7 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(false); - REQUIRE_CALL(matcher, describe(_)) - .LR_WITH(&_1 == &arg0) + REQUIRE_CALL(matcher, describe()) .RETURN("failed"); REQUIRE_CALL(describer, Invoke(_, "failed", false)) .LR_WITH(&_1 == &arg0) @@ -1094,8 +1092,7 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(true); - REQUIRE_CALL(matcher, describe(_)) - .LR_WITH(&_1 == &arg0) + REQUIRE_CALL(matcher, describe()) .RETURN("custom requirement"); const call::SubMatchResult result = std::as_const(policy).matches(info); @@ -1111,8 +1108,7 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(false); - REQUIRE_CALL(matcher, describe(_)) - .LR_WITH(&_1 == &arg0) + REQUIRE_CALL(matcher, describe()) .RETURN("custom requirement"); const call::SubMatchResult result = std::as_const(policy).matches(info); diff --git a/test/Matcher.cpp b/test/Matcher.cpp index 5a045e76e..a5c16847e 100644 --- a/test/Matcher.cpp +++ b/test/Matcher.cpp @@ -57,8 +57,8 @@ TEST_CASE( MatcherPredicateMock predicate{}; PredicateMatcher matcher{ std::ref(predicate), - "Hello, {}!", - std::tuple<>{} + "Hello, World!", + "not Hello, World!" }; SECTION("When matches() is called, argument is forwarded to the predicate.") @@ -72,10 +72,9 @@ TEST_CASE( REQUIRE(result == matcher.matches(value)); } - SECTION("When describe() is called, argument is forwarded to the functional.") + SECTION("When describe() is called.") { - constexpr int value{42}; - REQUIRE("Hello, 42!" == matcher.describe(value)); + REQUIRE("Hello, World!" == matcher.describe()); } } @@ -88,7 +87,8 @@ TEST_CASE( MatcherPredicateMock predicate{}; PredicateMatcher matcher{ std::ref(predicate), - "Hello, {}!" + "Hello, World!", + "not Hello, World!" }; PredicateMatcher negatedMatcher = !std::move(matcher); @@ -105,10 +105,9 @@ TEST_CASE( REQUIRE(result == !negatedMatcher.matches(value)); } - SECTION("When describe() is called, argument is forwarded to the functional.") + SECTION("When describe() is called.") { - constexpr int value{42}; - REQUIRE("not (Hello, 42!)" == negatedMatcher.describe(value)); + REQUIRE("not Hello, World!" == negatedMatcher.describe()); } } @@ -117,7 +116,8 @@ TEST_CASE( MatcherPredicateMock predicate{}; const PredicateMatcher matcher{ std::ref(predicate), - "Hello, {}!" + "Hello, World!", + "not Hello, World!" }; PredicateMatcher negatedMatcher = !matcher; @@ -134,10 +134,9 @@ TEST_CASE( REQUIRE(result == !negatedMatcher.matches(value)); } - SECTION("When describe() is called, argument is forwarded to the functional.") + SECTION("When describe() is called.") { - constexpr int value{42}; - REQUIRE("not (Hello, 42!)" == negatedMatcher.describe(value)); + REQUIRE("not Hello, World!" == negatedMatcher.describe()); } SECTION("And original matcher is still working.") @@ -153,10 +152,9 @@ TEST_CASE( REQUIRE(result == matcher.matches(value)); } - SECTION("When describe() is called, argument is forwarded to the functional.") + SECTION("When describe() is called.") { - constexpr int value{42}; - REQUIRE("Hello, 42!" == matcher.describe(value)); + REQUIRE("Hello, World!" == matcher.describe()); } } } @@ -174,9 +172,8 @@ TEST_CASE( constexpr int value{42}; REQUIRE(matches::_.matches(value)); REQUIRE_THAT( - matches::_.describe(value), - Catch::Matchers::EndsWith(" without constraints") - && Catch::Matchers::StartsWith("42")); + matches::_.describe(), + Catch::Matchers::Equals("has no constraints")); } TEST_CASE( @@ -185,49 +182,40 @@ TEST_CASE( ) { const auto matcher = matches::eq(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("== 42")); SECTION("When target is equal.") { constexpr int target{42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::EndsWith(" == 42") - && Catch::Matchers::StartsWith("42")); } SECTION("When target is not equal.") { constexpr int target{1337}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::EndsWith(" == 42") - && Catch::Matchers::StartsWith("1337")); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::eq(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("!= 42")); + SECTION("When target is equal.") { constexpr int target{42}; REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::EndsWith(" == 42)") - && Catch::Matchers::StartsWith("not (42")); } SECTION("When target is not equal.") { constexpr int target{1337}; REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::EndsWith(" == 42)") - && Catch::Matchers::StartsWith("not (1337")); } } } @@ -239,48 +227,40 @@ TEST_CASE( { const auto matcher = matches::ne(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("!= 42")); + SECTION("When target is not equal.") { constexpr int target{1337}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::EndsWith(" != 42") - && Catch::Matchers::StartsWith("1337")); } SECTION("When target is equal.") { constexpr int target{42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::EndsWith(" != 42") - && Catch::Matchers::StartsWith("42")); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::ne(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("== 42")); + SECTION("When target is not equal.") { constexpr int target{1337}; REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::EndsWith(" != 42)") - && Catch::Matchers::StartsWith("not (1337")); } SECTION("When target is equal.") { constexpr int target{42}; REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::EndsWith(" != 42)") - && Catch::Matchers::StartsWith("not (42")); } } } @@ -292,44 +272,40 @@ TEST_CASE( { const auto matcher = matches::lt(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("< 42")); + SECTION("When target is less.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41); REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} < 42", target))); } SECTION("When target is not less.") { const int target = GENERATE(42, 43, std::numeric_limits::max()); REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} < 42", target))); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::lt(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals(">= 42")); + SECTION("When target is less.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41); REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} < 42)", target))); } SECTION("When target is not less.") { const int target = GENERATE(42, 43, std::numeric_limits::max()); REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} < 42)", target))); } } } @@ -341,44 +317,40 @@ TEST_CASE( { const auto matcher = matches::le(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("<= 42")); + SECTION("When target is less or equal.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 42); REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} <= 42", target))); } SECTION("When target is greater.") { const int target = GENERATE(43, std::numeric_limits::max()); REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} <= 42", target))); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::le(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("> 42")); + SECTION("When target is less or equal.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 42); REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} <= 42)", target))); } SECTION("When target is greater.") { const int target = GENERATE(43, std::numeric_limits::max()); REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} <= 42)", target))); } } } @@ -390,44 +362,40 @@ TEST_CASE( { const auto matcher = matches::gt(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("> 42")); + SECTION("When target is greater.") { const int target = GENERATE(43, std::numeric_limits::max()); REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} > 42", target))); } SECTION("When target is not greater.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41, 42); REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} > 42", target))); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::gt(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("<= 42")); + SECTION("When target is greater.") { const int target = GENERATE(43, std::numeric_limits::max()); REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} > 42)", target))); } SECTION("When target is not greater.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41, 42); REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} > 42)", target))); } } } @@ -439,44 +407,40 @@ TEST_CASE( { const auto matcher = matches::ge(42); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals(">= 42")); + SECTION("When target is greater or equal.") { const int target = GENERATE(42, 43, std::numeric_limits::max()); REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} >= 42", target))); } SECTION("When target is less.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41); REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} >= 42", target))); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::ge(42); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("< 42")); + SECTION("When target is greater or equal.") { const int target = GENERATE(42, 43, std::numeric_limits::max()); REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} >= 42)", target))); } SECTION("When target is less.") { const int target = GENERATE(std::numeric_limits::min(), -1, 0, 1, 41); REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} >= 42)", target))); } } } @@ -499,36 +463,42 @@ TEST_CASE( const auto matcher = matches::predicate(std::ref(predicate)); REQUIRE(expectedResult == matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals(format::format("{} satisfies predicate", target))); + matcher.describe(), + Catch::Matchers::Equals("passes predicate")); SECTION("When matcher is inverted.") { const auto invertedMatcher = !matches::predicate(std::ref(predicate)); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("fails predicate")); + REQUIRE_CALL(predicate, Invoke(_)) .LR_WITH(&_1 == &target) .RETURN(expectedResult); REQUIRE(expectedResult == !invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals(format::format("not ({} satisfies predicate)", target))); } SECTION("Custom descriptions are supported.") { const auto customMatcher = matches::predicate( std::ref(predicate), - "custom predicate is satisfied"); + "custom predicate is passed", + "custom predicate is failed"); REQUIRE_CALL(predicate, Invoke(_)) .LR_WITH(&_1 == &target) .RETURN(expectedResult); REQUIRE(expectedResult == customMatcher.matches(target)); REQUIRE_THAT( - customMatcher.describe(target), - Catch::Matchers::Equals("custom predicate is satisfied")); + customMatcher.describe(), + Catch::Matchers::Equals("custom predicate is passed")); + + REQUIRE_THAT( + (!customMatcher).describe(), + Catch::Matchers::Equals("custom predicate is failed")); } } @@ -540,15 +510,15 @@ TEST_CASE( using trompeloeil::_; const auto matcher = matches::str::eq("Hello, World!"); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is equal to \"Hello, World!\"")); SECTION("When target is equal, they match.") { const std::string target{"Hello, World!"}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("string \"Hello, World!\" is equal to \"Hello, World!\"")); } SECTION("When target is not equal, they do not match.") @@ -556,23 +526,21 @@ TEST_CASE( const std::string target{"Hello, WOrld!"}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("string \"Hello, WOrld!\" is equal to \"Hello, World!\"")); } SECTION("Matcher can be inverted.") { const auto invertedMatcher = !matches::str::eq("Hello, World!"); + REQUIRE_THAT( + invertedMatcher.describe(), + Catch::Matchers::Equals("is not equal to \"Hello, World!\"")); + SECTION("When target is equal, they do not match.") { const std::string target{"Hello, World!"}; REQUIRE(!invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals("not (string \"Hello, World!\" is equal to \"Hello, World!\")")); } SECTION("When target is not equal, they do match.") @@ -580,9 +548,6 @@ TEST_CASE( const std::string target{"Hello, WOrld!"}; REQUIRE(invertedMatcher.matches(target)); - REQUIRE_THAT( - invertedMatcher.describe(target), - Catch::Matchers::Equals("not (string \"Hello, WOrld!\" is equal to \"Hello, World!\")")); } } } @@ -598,14 +563,15 @@ TEST_CASE( { const auto matcher = matches::range::eq(std::vector{}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("elements are { }")); + SECTION("When target is also empty, they match.") { const std::vector target{}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { } is equal to { }")); } SECTION("When target is not empty, they do not match.") @@ -613,9 +579,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42 } is equal to { }")); } } @@ -623,14 +586,15 @@ TEST_CASE( { const auto matcher = matches::range::eq(std::vector{1337, 42}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("elements are { 1337, 42 }")); + SECTION("When target is equal, they match.") { const std::vector target{1337, 42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is equal to { 1337, 42 }")); } SECTION("When target has same elements, but in different order, they do not match.") @@ -638,9 +602,6 @@ TEST_CASE( const std::vector target{42, 1337}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42, 1337 } is equal to { 1337, 42 }")); } SECTION("When target is not equal, they do not match.") @@ -648,9 +609,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42 } is equal to { 1337, 42 }")); } } @@ -658,14 +616,15 @@ TEST_CASE( { const auto matcher = !matches::range::eq(std::vector{1337, 42}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("elements are not { 1337, 42 }")); + SECTION("When target is equal, they do not match.") { const std::vector target{1337, 42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 1337, 42 } is equal to { 1337, 42 })")); } SECTION("When target has same elements, but in different order, they do match.") @@ -673,9 +632,6 @@ TEST_CASE( const std::vector target{42, 1337}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42, 1337 } is equal to { 1337, 42 })")); } SECTION("When target is not equal, they do match.") @@ -683,9 +639,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42 } is equal to { 1337, 42 })")); } } @@ -706,8 +659,8 @@ TEST_CASE( REQUIRE(matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is equal to { 1337, 42 }")); + matcher.describe(), + Catch::Matchers::Equals("elements are { 1337, 42 }")); } } @@ -722,14 +675,15 @@ TEST_CASE( { const auto matcher = matches::range::unordered_eq(std::vector{}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is a permutation of { }")); + SECTION("When target is also empty, they match.") { const std::vector target{}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { } is permutation of { }")); } SECTION("When target is not empty, they do not match.") @@ -737,9 +691,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42 } is permutation of { }")); } } @@ -747,14 +698,15 @@ TEST_CASE( { const auto matcher = matches::range::unordered_eq(std::vector{1337, 42}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is a permutation of { 1337, 42 }")); + SECTION("When target is equal, they match.") { const std::vector target{1337, 42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is permutation of { 1337, 42 }")); } SECTION("When target has same elements, but in different order, they do match.") @@ -762,9 +714,6 @@ TEST_CASE( const std::vector target{42, 1337}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42, 1337 } is permutation of { 1337, 42 }")); } SECTION("When target is not equal, they do not match.") @@ -772,9 +721,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42 } is permutation of { 1337, 42 }")); } } @@ -782,14 +728,15 @@ TEST_CASE( { const auto matcher = !matches::range::unordered_eq(std::vector{1337, 42}); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is not a permutation of { 1337, 42 }")); + SECTION("When target is equal, they do not match.") { const std::vector target{1337, 42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 1337, 42 } is permutation of { 1337, 42 })")); } SECTION("When target has same elements, but in different order, they do not match.") @@ -797,9 +744,6 @@ TEST_CASE( const std::vector target{42, 1337}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42, 1337 } is permutation of { 1337, 42 })")); } SECTION("When target is not equal, they do match.") @@ -807,9 +751,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42 } is permutation of { 1337, 42 })")); } } @@ -821,6 +762,10 @@ TEST_CASE( std::vector{1337, 42}, std::ref(comparator)); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is a permutation of { 1337, 42 }")); + const std::vector target{1337, 42}; REQUIRE_CALL(comparator, Invoke(1337, 1337)) @@ -829,9 +774,6 @@ TEST_CASE( .RETURN(true); REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is permutation of { 1337, 42 }")); } } @@ -850,22 +792,23 @@ TEST_CASE( REQUIRE(matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { } is sorted")); + matcher.describe(), + Catch::Matchers::Equals("is a sorted range")); } SECTION("When a non-empty range is stored.") { const auto matcher = matches::range::is_sorted(); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is a sorted range")); + SECTION("When target is sorted, it's a match.") { const std::vector target{42, 1337}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42, 1337 } is sorted")); } SECTION("When target is not sorted, it's no match.") @@ -873,9 +816,6 @@ TEST_CASE( const std::vector target{1337, 42}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is sorted")); } } @@ -883,14 +823,15 @@ TEST_CASE( { const auto matcher = !matches::range::is_sorted(); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is an unsorted range")); + SECTION("When target is sorted, it's no match.") { const std::vector target{42, 1337}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42, 1337 } is sorted)")); } SECTION("When target is not sorted, it's a match.") @@ -898,9 +839,6 @@ TEST_CASE( const std::vector target{1337, 42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 1337, 42 } is sorted)")); } } @@ -918,8 +856,8 @@ TEST_CASE( REQUIRE(matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 1337, 42 } is sorted")); + matcher.describe(), + Catch::Matchers::Equals("is a sorted range")); } } @@ -938,8 +876,8 @@ TEST_CASE( REQUIRE(matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { } is empty")); + matcher.describe(), + Catch::Matchers::Equals("is an empty range")); } SECTION("When a non-empty range is stored, it's no match.") @@ -950,22 +888,23 @@ TEST_CASE( REQUIRE(!matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42 } is empty")); + matcher.describe(), + Catch::Matchers::Equals("is an empty range")); } SECTION("Matcher can be inverted.") { const auto matcher = !matches::range::is_empty(); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("is not an empty range")); + SECTION("When target is empty, it's no match.") { const std::vector target{}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { } is empty)")); } SECTION("When a non-empty range is stored, it's a match.") @@ -973,9 +912,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42 } is empty)")); } } } @@ -994,8 +930,8 @@ TEST_CASE( REQUIRE(matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("range { 42, 1337 } has size 2")); + matcher.describe(), + Catch::Matchers::Equals("has size of 2")); } SECTION("When target has different size, it's no match.") @@ -1007,25 +943,23 @@ TEST_CASE( REQUIRE(!matcher.matches(target)); REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals( - format::format( - "range {} has size 1", - mimicpp::print(target)))); + matcher.describe(), + Catch::Matchers::Equals("has size of 1")); } SECTION("Matcher can be inverted.") { const auto matcher = !matches::range::has_size(2); + REQUIRE_THAT( + matcher.describe(), + Catch::Matchers::Equals("has different size than 2")); + SECTION("When target has the expected size, it's no match.") { const std::vector target{42, 1337}; REQUIRE(!matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42, 1337 } has size 2)")); } SECTION("When target has different size, it's a match.") @@ -1033,9 +967,6 @@ TEST_CASE( const std::vector target{42}; REQUIRE(matcher.matches(target)); - REQUIRE_THAT( - matcher.describe(target), - Catch::Matchers::Equals("not (range { 42 } has size 2)")); } } } diff --git a/test/TestTypes.hpp b/test/TestTypes.hpp index 83455996d..02e0b608b 100644 --- a/test/TestTypes.hpp +++ b/test/TestTypes.hpp @@ -212,7 +212,7 @@ class MatcherMock { public: MAKE_CONST_MOCK1(matches, bool(T)); - MAKE_CONST_MOCK1(describe, mimicpp::StringT(T)); + MAKE_CONST_MOCK0(describe, mimicpp::StringT()); }; template @@ -234,12 +234,11 @@ class [[maybe_unused]] MatcherFacade .matches(std::forward(target)); } - template [[nodiscard]] - constexpr mimicpp::StringT describe(T&& target) const + constexpr mimicpp::StringT describe() const { return std::invoke(m_Projection, m_Matcher) - .describe(std::forward(target)); + .describe(); } private: From bf80476ac40587b51aafb4b496273e9d0bb00f17 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 00:10:54 +0200 Subject: [PATCH 04/68] fix: add missing exception include --- test/TestReporter.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/TestReporter.hpp b/test/TestReporter.hpp index 8962a8f0b..b83df3e21 100644 --- a/test/TestReporter.hpp +++ b/test/TestReporter.hpp @@ -8,6 +8,7 @@ #include "mimic++/Fwd.hpp" #include +#include #include #include #include From 3e1aa30c15028e9ce9453a3e01cd2535bed170f6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 00:11:07 +0200 Subject: [PATCH 05/68] fix: remove several constexpr from functions --- include/mimic++/Reporter.hpp | 2 +- test/TestReporter.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 69c52adef..462fb5b68 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -61,7 +61,7 @@ namespace mimicpp } template - static constexpr void report_unhandled_exception( + static void report_unhandled_exception( const call::Info& callInfo, std::shared_ptr> expectation, std::exception_ptr exception diff --git a/test/TestReporter.hpp b/test/TestReporter.hpp index b83df3e21..7a47f43c6 100644 --- a/test/TestReporter.hpp +++ b/test/TestReporter.hpp @@ -47,7 +47,7 @@ class TestReporter static void report_error(const mimicpp::StringT& message); template - static constexpr void report_unhandled_exception( + static void report_unhandled_exception( const mimicpp::call::Info& callInfo, std::shared_ptr> expectation, std::exception_ptr exception @@ -121,7 +121,7 @@ inline void TestReporter::report_error(const mimicpp::StringT& message) } template -constexpr void TestReporter::report_unhandled_exception( +void TestReporter::report_unhandled_exception( const mimicpp::call::Info& callInfo, std::shared_ptr> expectation, std::exception_ptr exception From 32d2a6bca2d4b6e3dc1a540c914d188b8eb8a250 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 23:15:46 +0200 Subject: [PATCH 06/68] refactor: divide matches into matches and describe functions --- include/mimic++/Call.hpp | 8 +- include/mimic++/Expectation.hpp | 5 +- include/mimic++/ExpectationPolicies.hpp | 101 ++++++++++----------- include/mimic++/Sequence.hpp | 1 + test/Expectation.cpp | 18 ++-- test/ExpectationPolicies.cpp | 111 +++++++++--------------- test/TestTypes.hpp | 26 ++++-- 7 files changed, 122 insertions(+), 148 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index 0aa04ea0e..f24bfd647 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -115,7 +115,6 @@ namespace mimicpp::call { public: bool matched{}; - std::optional msg{}; [[nodiscard]] friend bool operator ==(const SubMatchResult&, const SubMatchResult&) = default; @@ -136,26 +135,23 @@ namespace mimicpp::call namespace mimicpp::call::detail { [[nodiscard]] - inline MatchResultT evaluate_sub_match_results(const bool isApplicable, std::vector subResults) noexcept + inline MatchResultT evaluate_sub_match_results(const bool isApplicable, const std::vector& subResults) noexcept { static_assert(3 == std::variant_size_v, "Unexpected MatchResult alternative count."); - if (!std::ranges::all_of(subResults, &SubMatchResult::matched)) + if (!std::ranges::all_of(subResults, std::bind_front(std::equal_to{}, true))) { return MatchResult_NoT{ - .subMatchResults = std::move(subResults) }; } if (!isApplicable) { return MatchResult_NotApplicableT{ - .subMatchResults = std::move(subResults) }; } return MatchResult_OkT{ - .subMatchResults = std::move(subResults) }; } } diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 1710db66a..bb67d7c68 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -193,7 +193,8 @@ namespace mimicpp && requires(T& policy, const call::info_for_signature_t& info) { { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to; - { std::as_const(policy).matches(info) } -> std::convertible_to; + { std::as_const(policy).matches(info) } -> std::convertible_to; + { std::as_const(policy).describe() } -> std::convertible_to>; { policy.consume(info) }; }; @@ -271,7 +272,7 @@ namespace mimicpp std::apply( [&](const auto&... policies) { - return std::vector{ + return std::vector{ policies.matches(call)... }; }, diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index 54e6f8bf7..1c502d796 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -107,20 +107,9 @@ namespace mimicpp::expectation_policies } template - static call::SubMatchResult matches(const call::Info& info) + static constexpr bool matches(const call::Info& info) noexcept { - if (mimicpp::is_matching(info.fromCategory, expected)) - { - return { - .matched = true, - .msg = format::format(" matches Category {}", expected) - }; - } - - return { - .matched = false, - .msg = format::format(" does not match Category {}", expected) - }; + return mimicpp::is_matching(info.fromCategory, expected); } template @@ -128,6 +117,14 @@ namespace mimicpp::expectation_policies { assert(mimicpp::is_matching(info.fromCategory, expected) && "Call does not match."); } + + [[nodiscard]] + static StringT describe() + { + return format::format( + "expect: from {} category overload", + expected); + } }; template @@ -140,20 +137,9 @@ namespace mimicpp::expectation_policies } template - static constexpr call::SubMatchResult matches(const call::Info& info) noexcept + static constexpr bool matches(const call::Info& info) noexcept { - if (mimicpp::is_matching(info.fromConstness, constness)) - { - return { - .matched = true, - .msg = format::format(" matches Constness {}", constness) - }; - } - - return { - .matched = false, - .msg = format::format(" does not match Constness {}", constness) - }; + return mimicpp::is_matching(info.fromConstness, constness); } template @@ -161,6 +147,14 @@ namespace mimicpp::expectation_policies { assert(mimicpp::is_matching(info.fromConstness, constness) && "Call does not match."); } + + [[nodiscard]] + static StringT describe() + { + return format::format( + "expect: from {} qualified overload", + constness); + } }; template @@ -255,32 +249,14 @@ namespace mimicpp::expectation_policies template requires std::invocable&> - && requires(std::invoke_result_t&> target) - { - requires matcher_for< - Matcher, - decltype(target)>; - requires std::convertible_to< - std::invoke_result_t< - const Describer&, - decltype(target), - StringT, - bool>, - std::optional>; - } + && matcher_for< + Matcher, + std::invoke_result_t&>> [[nodiscard]] - constexpr call::SubMatchResult matches(const call::Info& info) const + constexpr bool matches(const call::Info& info) const { - auto& target = std::invoke(m_Projection, info); - const bool matchResult = m_Matcher.matches(target); - return { - .matched = matchResult, - .msg = std::invoke( - m_Describer, - target, - m_Matcher.describe(), - matchResult) - }; + return m_Matcher.matches( + std::invoke(m_Projection, info)); } template @@ -288,6 +264,14 @@ namespace mimicpp::expectation_policies { } + [[nodiscard]] + StringT describe() const + { + return std::invoke( + m_Describer, + m_Matcher.describe()); + } + private: [[no_unique_address]] Matcher m_Matcher; [[no_unique_address]] Projection m_Projection; @@ -322,9 +306,15 @@ namespace mimicpp::expectation_policies template [[nodiscard]] - static constexpr call::SubMatchResult matches(const call::Info&) noexcept + static constexpr bool matches(const call::Info&) noexcept + { + return true; + } + + [[nodiscard]] + static std::nullopt_t describe() noexcept { - return {true}; + return std::nullopt; } template @@ -514,15 +504,12 @@ namespace mimicpp::expect { [[nodiscard]] constexpr StringT operator ()( - [[maybe_unused]] auto&& target, - const StringViewT matcherDescription, - const bool result + const StringViewT matcherDescription ) const { return format::format( - "arg[{}] {} requirement: {}", + "expect: arg[{}] {}", index, - result ? "passed" : "failed", matcherDescription); } }; diff --git a/include/mimic++/Sequence.hpp b/include/mimic++/Sequence.hpp index 534646b62..6a16d65f1 100644 --- a/include/mimic++/Sequence.hpp +++ b/include/mimic++/Sequence.hpp @@ -8,6 +8,7 @@ #pragma once +#include "mimic++/Printer.hpp" #include "mimic++/Reporter.hpp" #include "mimic++/Utility.hpp" diff --git a/test/Expectation.cpp b/test/Expectation.cpp index df1e3c737..7a04003a2 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -463,7 +463,7 @@ TEST_CASE( .RETURN(isApplicable); REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{false}); + .RETURN(false); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } @@ -475,7 +475,7 @@ TEST_CASE( .RETURN(true); REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{true}); + .RETURN(true); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } @@ -485,7 +485,7 @@ TEST_CASE( .RETURN(false); REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{true}); + .RETURN(true); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } } @@ -549,7 +549,7 @@ TEMPLATE_TEST_CASE( { REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{true}); + .RETURN(true); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } @@ -557,7 +557,7 @@ TEMPLATE_TEST_CASE( { REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{false}); + .RETURN(false); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } @@ -602,10 +602,10 @@ TEMPLATE_TEST_CASE( { REQUIRE_CALL(policy1, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{true}); + .RETURN(true); REQUIRE_CALL(policy2, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{true}); + .RETURN(true); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } @@ -621,10 +621,10 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy1, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{match1}); + .RETURN(match1); REQUIRE_CALL(policy2, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(mimicpp::call::SubMatchResult{match2}); + .RETURN(match2); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } diff --git a/test/ExpectationPolicies.cpp b/test/ExpectationPolicies.cpp index 9054670d2..53fb7de6e 100644 --- a/test/ExpectationPolicies.cpp +++ b/test/ExpectationPolicies.cpp @@ -190,6 +190,13 @@ TEMPLATE_TEST_CASE_SIG( REQUIRE(policy.is_satisfied()); } + SECTION("Policy description.") + { + REQUIRE_THAT( + policy.describe(), + Catch::Matchers::Equals(format::format("expect: from {} category overload", category))); + } + const CallInfoT call{ .args = {}, .fromCategory = GENERATE(ValueCategory::lvalue, ValueCategory::rvalue, ValueCategory::any), @@ -200,12 +207,7 @@ TEMPLATE_TEST_CASE_SIG( { SECTION("When call and policy category matches, success is returned.") { - const auto result = policy.matches(call); - - REQUIRE(result.matched); - REQUIRE_THAT( - result.msg.value(), - Catch::Matchers::Equals(format::format(" matches Category {}", category))); + REQUIRE(policy.matches(call)); } SECTION("Policy doesn't consume, but asserts on wrong category.") @@ -217,12 +219,7 @@ TEMPLATE_TEST_CASE_SIG( { SECTION("When call and policy category mismatch, failure is returned.") { - const auto result = policy.matches(call); - - REQUIRE(!result.matched); - REQUIRE_THAT( - result.msg.value(), - Catch::Matchers::Equals(format::format(" does not match Category {}", category))); + REQUIRE(!policy.matches(call)); } } } @@ -247,6 +244,13 @@ TEMPLATE_TEST_CASE_SIG( REQUIRE(policy.is_satisfied()); } + SECTION("Policy description.") + { + REQUIRE_THAT( + policy.describe(), + Catch::Matchers::Equals(format::format("expect: from {} qualified overload", constness))); + } + const CallInfoT call{ .args = {}, .fromCategory = GENERATE(ValueCategory::lvalue, ValueCategory::rvalue, ValueCategory::any), @@ -257,12 +261,7 @@ TEMPLATE_TEST_CASE_SIG( { SECTION("When call and policy constness matches, success is returned.") { - const auto result = policy.matches(call); - - REQUIRE(result.matched); - REQUIRE_THAT( - result.msg.value(), - Catch::Matchers::Equals(format::format(" matches Constness {}", constness))); + REQUIRE(policy.matches(call)); } SECTION("Policy doesn't consume, but asserts on wrong constness.") @@ -274,12 +273,7 @@ TEMPLATE_TEST_CASE_SIG( { SECTION("When call and policy constness mismatch, failure is returned.") { - const auto result = policy.matches(call); - - REQUIRE(!result.matched); - REQUIRE_THAT( - result.msg.value(), - Catch::Matchers::Equals(format::format(" does not match Constness {}", constness))); + REQUIRE(!policy.matches(call)); } } } @@ -380,7 +374,7 @@ TEST_CASE( }; using ProjectionT = InvocableMock; - using DescriberT = InvocableMock; + using DescriberT = InvocableMock; using MatcherT = MatcherMock; STATIC_CHECK(matcher_for); @@ -403,6 +397,18 @@ TEST_CASE( REQUIRE(std::as_const(policy).is_satisfied()); REQUIRE_NOTHROW(policy.consume(info)); + SECTION("Policy description.") + { + REQUIRE_CALL(matcher, describe()) + .RETURN("matcher description"); + REQUIRE_CALL(describer, Invoke("matcher description")) + .RETURN("expect that: matcher description"); + + REQUIRE_THAT( + policy.describe(), + Catch::Matchers::Equals("expect that: matcher description")); + } + SECTION("When matched.") { REQUIRE_CALL(projection, Invoke(_)) @@ -411,18 +417,8 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(true); - REQUIRE_CALL(matcher, describe()) - .RETURN("success"); - REQUIRE_CALL(describer, Invoke(_, "success", true)) - .LR_WITH(&_1 == &arg0) - .RETURN("succeeded!"); - const call::SubMatchResult result = std::as_const(policy).matches(info); - REQUIRE(result.matched); - REQUIRE(result.msg); - REQUIRE_THAT( - *result.msg, - Matches::Equals("succeeded!")); + REQUIRE(std::as_const(policy).matches(info)); } SECTION("When not matched.") @@ -433,18 +429,8 @@ TEST_CASE( REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) .RETURN(false); - REQUIRE_CALL(matcher, describe()) - .RETURN("failed"); - REQUIRE_CALL(describer, Invoke(_, "failed", false)) - .LR_WITH(&_1 == &arg0) - .RETURN("failure!"); - const call::SubMatchResult result = std::as_const(policy).matches(info); - REQUIRE(!result.matched); - REQUIRE(result.msg); - REQUIRE_THAT( - *result.msg, - Matches::Equals("failure!")); + REQUIRE(!std::as_const(policy).matches(info)); } } @@ -481,7 +467,8 @@ TEST_CASE( expectation_policies::SideEffectAction policy{std::ref(action)}; STATIC_REQUIRE(expectation_policy_for); REQUIRE(std::as_const(policy).is_satisfied()); - REQUIRE(call::SubMatchResult{true} == std::as_const(policy).matches(info)); + REQUIRE(std::as_const(policy).matches(info)); + REQUIRE(std::optional{} == std::as_const(policy).describe()); REQUIRE_CALL(action, Invoke(_)) .LR_WITH(&info == &_1); @@ -1087,36 +1074,24 @@ TEST_CASE( REQUIRE(std::as_const(policy).is_satisfied()); REQUIRE_NOTHROW(policy.consume(info)); - SECTION("When matched.") + SECTION("Policy description.") { - REQUIRE_CALL(matcher, matches(_)) - .LR_WITH(&_1 == &arg0) - .RETURN(true); REQUIRE_CALL(matcher, describe()) - .RETURN("custom requirement"); + .RETURN("matcher description"); - const call::SubMatchResult result = std::as_const(policy).matches(info); - REQUIRE(result.matched); - REQUIRE(result.msg); REQUIRE_THAT( - *result.msg, - Matches::Equals("arg[0] passed requirement: custom requirement")); + policy.describe(), + Catch::Matchers::Equals("expect: arg[0] matcher description")); } - SECTION("When not matched.") + SECTION("Policy matches().") { + const bool match = GENERATE(true, false); REQUIRE_CALL(matcher, matches(_)) .LR_WITH(&_1 == &arg0) - .RETURN(false); - REQUIRE_CALL(matcher, describe()) - .RETURN("custom requirement"); + .RETURN(match); - const call::SubMatchResult result = std::as_const(policy).matches(info); - REQUIRE(!result.matched); - REQUIRE(result.msg); - REQUIRE_THAT( - *result.msg, - Matches::Equals("arg[0] failed requirement: custom requirement")); + REQUIRE(match == std::as_const(policy).matches(info)); } } diff --git a/test/TestTypes.hpp b/test/TestTypes.hpp index 02e0b608b..1f9464de8 100644 --- a/test/TestTypes.hpp +++ b/test/TestTypes.hpp @@ -26,7 +26,6 @@ class PolicyFake { public: using CallInfoT = mimicpp::call::info_for_signature_t; - using SubMatchT = mimicpp::call::SubMatchResult; bool isSatisfied{}; @@ -36,14 +35,22 @@ class PolicyFake return isSatisfied; } - SubMatchT matchResult{}; + bool matchResult{}; [[nodiscard]] - constexpr SubMatchT matches(const CallInfoT& call) const noexcept + constexpr bool matches(const CallInfoT& call) const noexcept { return matchResult; } + mimicpp::StringT description{}; + + [[nodiscard]] + mimicpp::StringT describe() const + { + return description; + } + static constexpr void consume(const CallInfoT& call) noexcept { } @@ -81,12 +88,19 @@ class PolicyFacade } [[nodiscard]] - constexpr SubMatchT matches(const CallT& call) const noexcept + constexpr bool matches(const CallT& call) const noexcept { return std::invoke(projection, policy) .matches(call); } + [[nodiscard]] + mimicpp::StringT describe() const + { + return std::invoke(projection, policy) + .describe(); + } + constexpr void consume(const CallT& call) noexcept { std::invoke(projection, policy) @@ -116,12 +130,12 @@ class PolicyMock { public: using CallInfoT = mimicpp::call::info_for_signature_t; - using SubMatchT = mimicpp::call::SubMatchResult; static constexpr bool trompeloeil_movable_mock = true; MAKE_CONST_MOCK0(is_satisfied, bool (), noexcept); - MAKE_CONST_MOCK1(matches, SubMatchT (const CallInfoT&), noexcept); + MAKE_CONST_MOCK1(matches, bool(const CallInfoT&), noexcept); + MAKE_CONST_MOCK0(describe, mimicpp::StringT()); MAKE_MOCK1(consume, void (const CallInfoT&), noexcept); }; From 170769607cb0b7a968a1b1e4586d37938935d8d5 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 23:34:58 +0200 Subject: [PATCH 07/68] refactor: simplify BasicExpectation::matches --- include/mimic++/Call.hpp | 23 ------------------- include/mimic++/Expectation.hpp | 26 ++++++++++++--------- test/Expectation.cpp | 40 +++++++++++++++++---------------- 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index f24bfd647..dc99248e4 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -132,29 +132,6 @@ namespace mimicpp::call }; } -namespace mimicpp::call::detail -{ - [[nodiscard]] - inline MatchResultT evaluate_sub_match_results(const bool isApplicable, const std::vector& subResults) noexcept - { - static_assert(3 == std::variant_size_v, "Unexpected MatchResult alternative count."); - - if (!std::ranges::all_of(subResults, std::bind_front(std::equal_to{}, true))) - { - return MatchResult_NoT{ - }; - } - - if (!isApplicable) - { - return MatchResult_NotApplicableT{ - }; - } - - return MatchResult_OkT{ - }; - } -} namespace mimicpp::detail { diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index bb67d7c68..806632d20 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -267,16 +267,22 @@ namespace mimicpp [[nodiscard]] call::MatchResultT matches(const CallInfoT& call) const override { - return call::detail::evaluate_sub_match_results( - m_Times.is_applicable(), - std::apply( - [&](const auto&... policies) - { - return std::vector{ - policies.matches(call)... - }; - }, - m_Policies)); + if (!std::apply( + [&](const auto&... policies) + { + return (... && policies.matches(call)); + }, + m_Policies)) + { + return call::MatchResult_NoT{}; + } + + if (!m_Times.is_applicable()) + { + return call::MatchResult_NotApplicableT{}; + } + + return call::MatchResult_OkT{}; } constexpr void consume(const CallInfoT& call) override diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 7a04003a2..44682f173 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -409,14 +409,14 @@ TEST_CASE( REQUIRE(isSatisfied == std::as_const(expectation).is_satisfied()); } - SECTION("When times is not saturated, call is matched.") + SECTION("When times is applicable, call is matched.") { REQUIRE_CALL(times, is_applicable()) .RETURN(true); REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } - SECTION("When times is saturated, match exhausted.") + SECTION("When times is not applicable => unapplicable match.") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); @@ -458,9 +458,6 @@ TEST_CASE( SECTION("When policy is not matched, then the result is always no match.") { - const bool isApplicable = GENERATE(false, true); - REQUIRE_CALL(times, is_applicable()) - .RETURN(isApplicable); REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(false); @@ -479,7 +476,7 @@ TEST_CASE( REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); } - SECTION("And when times is saturated => exhausted") + SECTION("And when times not applicable => unapplicable") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); @@ -612,21 +609,26 @@ TEMPLATE_TEST_CASE( SECTION("When at least one not matches => no match") { - const auto [match1, match2] = GENERATE( - (table)({ - {false, false}, - {false, true}, - {true, false}, - })); - - REQUIRE_CALL(policy1, matches(_)) - .LR_WITH(&_1 == &call) - .RETURN(match1); - REQUIRE_CALL(policy2, matches(_)) + SECTION("When first does not match, no other policy is checked.") + { + REQUIRE_CALL(policy1, matches(_)) .LR_WITH(&_1 == &call) - .RETURN(match2); + .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + } + + SECTION("When first matches, but second not.") + { + REQUIRE_CALL(policy1, matches(_)) + .LR_WITH(&_1 == &call) + .RETURN(true); + REQUIRE_CALL(policy2, matches(_)) + .LR_WITH(&_1 == &call) + .RETURN(false); + + REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + } } SECTION("When calling consume()") From 5062cea6d87c5aaebaf9477d088e4045527e9179 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Fri, 3 May 2024 23:58:06 +0200 Subject: [PATCH 08/68] feat: unreachable helper function --- include/mimic++/Utility.hpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/mimic++/Utility.hpp b/include/mimic++/Utility.hpp index ccdd05cea..298fa9971 100644 --- a/include/mimic++/Utility.hpp +++ b/include/mimic++/Utility.hpp @@ -142,6 +142,25 @@ namespace mimicpp { return static_cast>(value); } + +#ifdef __cpp_lib_unreachable + using std::unreachable; +#else + + // taken from: https://en.cppreference.com/w/cpp/utility/unreachable + [[noreturn]] + inline void unreachable() + { + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. + #if defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); + #else // GCC, Clang + __builtin_unreachable(); + #endif + } +#endif } #endif From d45287aba2aa71cf9794d6474e4067d82f4cd6f7 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 13:07:46 +0200 Subject: [PATCH 09/68] feat: call_report --- include/mimic++/Reporter.hpp | 52 ++++++++- test/Reporter.cpp | 214 +++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 test/Reporter.cpp diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 462fb5b68..21cfb9d87 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -14,17 +14,61 @@ #include #include +#include #include namespace mimicpp { - template - class Expectation; + struct call_report + { + struct arg + { + std::type_index typeIndex; + StringT stateString; - class DefaultReporter + [[nodiscard]] + friend bool operator ==(const arg&, const arg&) = default; + }; + + std::type_index returnTypeIndex; + std::vector argDetails{}; + std::source_location fromLoc{}; + ValueCategory fromCategory{}; + Constness fromConstness{}; + + [[nodiscard]] + friend bool operator ==(const call_report& lhs, const call_report& rhs) { - public: + return lhs.returnTypeIndex == rhs.returnTypeIndex + && lhs.argDetails == rhs.argDetails + && is_same_source_location(lhs.fromLoc, rhs.fromLoc) + && lhs.fromCategory == rhs.fromCategory + && lhs.fromConstness == rhs.fromConstness; + } + }; + template + [[nodiscard]] + call_report make_call_report(const call::Info& callInfo) + { + return call_report{ + .returnTypeIndex = typeid(Return), + .argDetails = std::apply( + [](auto&... args) + { + return std::vector{ + call_report::arg{ + .typeIndex = typeid(Params), + .stateString = mimicpp::print(args.get()) + }... + }; + }, + callInfo.args), + .fromLoc = callInfo.fromSourceLocation, + .fromCategory = callInfo.fromCategory, + .fromConstness = callInfo.fromConstness + }; + } [[noreturn]] static void report_fail( const call::Info& callInfo, diff --git a/test/Reporter.cpp b/test/Reporter.cpp new file mode 100644 index 000000000..6748bde70 --- /dev/null +++ b/test/Reporter.cpp @@ -0,0 +1,214 @@ +// // Copyright Dominic (DNKpp) Koepke 2024 - 2024. +// // Distributed under the Boost Software License, Version 1.0. +// // (See accompanying file LICENSE_1_0.txt or copy at +// // https://www.boost.org/LICENSE_1_0.txt) + +#include "mimic++/Reporter.hpp" + +#include +#include + +using namespace mimicpp; + +TEST_CASE( + "call_report::arg is equality comparable.", + "[reporting]" +) +{ + using arg_t = call_report::arg; + + const arg_t first{ + .typeIndex = typeid(int), + .stateString = "42" + }; + + const auto [expectedEquality, second] = GENERATE( + (table({ + {false, {typeid(int), "1337"}}, + {false, {typeid(short), "42"}}, + {true, {typeid(int), "42"}} + }))); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality == !(first != second)); + REQUIRE(expectedEquality == !(second!= first)); +} + +TEST_CASE( + "call_report is equality comparable.", + "[reporting]" +) +{ + const call_report first{ + .returnTypeIndex = typeid(std::string), + .argDetails = { + { + .typeIndex = typeid(int), + .stateString = "42" + } + }, + .fromLoc = std::source_location::current(), + .fromCategory = ValueCategory::any, + .fromConstness = Constness::any + }; + + SECTION("When both sides are equal, they compare equal.") + { + const call_report second{first}; + + REQUIRE(first == second); + REQUIRE(second == first); + REQUIRE(!(first != second)); + REQUIRE(!(second != first)); + } + + SECTION("When return type differs, they compare not equal.") + { + call_report second{first}; + + second.returnTypeIndex = GENERATE(as{}, typeid(void), typeid(std::string_view)); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When category differs, they compare not equal.") + { + call_report second{first}; + + second.fromCategory = GENERATE(ValueCategory::lvalue, ValueCategory::rvalue); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When constness differs, they compare not equal.") + { + call_report second{first}; + + second.fromConstness = GENERATE(Constness::as_const, Constness::non_const); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When source location differs, they compare not equal.") + { + call_report second{first}; + + second.fromLoc = std::source_location::current(); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When source location differs, they compare not equal.") + { + call_report second{first}; + + using arg_t = call_report::arg; + second.argDetails = GENERATE( + std::vector{}, + std::vector{ + (arg_t{.typeIndex = typeid(int), .stateString = "1337"}) + }, + std::vector{ + (arg_t{.typeIndex = typeid(int), .stateString = "42"}), + (arg_t{.typeIndex = typeid(int), .stateString = "1337"}) + }); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } +} + +TEST_CASE( + "make_call_report generates report from call info." + "[reporting]" +) +{ + SECTION("When call info has void return type.") + { + const call::Info info{ + .args = {}, + .fromCategory = GENERATE(ValueCategory::any, ValueCategory::lvalue, ValueCategory::rvalue), + .fromConstness = GENERATE(Constness::any, Constness::as_const, Constness::non_const), + .fromSourceLocation = std::source_location::current() + }; + + const call_report report = make_call_report(info); + + REQUIRE( + report == + call_report{ + .returnTypeIndex = typeid(void), + .argDetails = {}, + .fromLoc = info.fromSourceLocation, + .fromCategory = info.fromCategory, + .fromConstness = info.fromConstness + }); + } + + SECTION("When call info has non-void return type.") + { + const call::Info info{ + .args = {}, + .fromCategory = GENERATE(ValueCategory::any, ValueCategory::lvalue, ValueCategory::rvalue), + .fromConstness = GENERATE(Constness::any, Constness::as_const, Constness::non_const), + .fromSourceLocation = std::source_location::current() + }; + + const call_report report = make_call_report(info); + + REQUIRE( + report == + call_report{ + .returnTypeIndex = typeid(int), + .argDetails = {}, + .fromLoc = info.fromSourceLocation, + .fromCategory = info.fromCategory, + .fromConstness = info.fromConstness + }); + } + + SECTION("When call info has arbitrary args.") + { + const int arg0{1337}; + double arg1{4.2}; + std::string arg2{"Hello, World!"}; + const call::Info info{ + .args = {std::ref(arg0), std::ref(arg1), std::ref(arg2)}, + .fromCategory = GENERATE(ValueCategory::any, ValueCategory::lvalue, ValueCategory::rvalue), + .fromConstness = GENERATE(Constness::any, Constness::as_const, Constness::non_const), + .fromSourceLocation = std::source_location::current() + }; + + const call_report report = make_call_report(info); + + using arg_t = call_report::arg; + REQUIRE( + report == + call_report{ + .returnTypeIndex = typeid(void), + .argDetails = { + (arg_t{typeid(const int&), "1337"}), + (arg_t{typeid(double), "4.2"}), + (arg_t{typeid(std::string), "\"Hello, World!\""}) + }, + .fromLoc = info.fromSourceLocation, + .fromCategory = info.fromCategory, + .fromConstness = info.fromConstness + }); + } +} From 92b5f5dbc230b403d5065aa5dd11d772e2199e13 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 13:26:56 +0200 Subject: [PATCH 10/68] refactor: overhaul handle_call --- include/mimic++/Call.hpp | 1 - include/mimic++/Expectation.hpp | 80 ++++++++++--------------- include/mimic++/ExpectationPolicies.hpp | 2 +- include/mimic++/Fwd.hpp | 7 +++ include/mimic++/Utility.hpp | 13 ++-- test/CMakeLists.txt | 1 + test/Expectation.cpp | 70 +++++++++++----------- 7 files changed, 85 insertions(+), 89 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index dc99248e4..985e8c6ac 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -132,7 +132,6 @@ namespace mimicpp::call }; } - namespace mimicpp::detail { template diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 806632d20..d0de17d03 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -23,30 +23,8 @@ namespace mimicpp::detail { - template - [[noreturn]] - void handle_call_match_fail( - call::Info call, - std::vector noMatches, - std::vector partialMatches - ) - { - if (!std::ranges::empty(partialMatches)) - { - report_fail( - std::move(call), - std::move(partialMatches)); - } - else - { - report_fail( - std::move(call), - std::move(noMatches)); - } - } - template - std::optional make_match_result( + std::optional make_match_result( const call::Info& call, const std::shared_ptr>& expectation ) noexcept @@ -90,7 +68,7 @@ namespace mimicpp virtual bool is_satisfied() const noexcept = 0; [[nodiscard]] - virtual call::MatchResultT matches(const CallInfoT& call) const = 0; + virtual call::MatchResult matches(const CallInfoT& call) const = 0; virtual void consume(const CallInfoT& call) = 0; [[nodiscard]] @@ -138,47 +116,53 @@ namespace mimicpp if (!expectation->is_satisfied()) { - report_unsatisfied_expectation(std::move(expectation)); + detail::report_unfulfilled_expectation( + std::move(expectation)); } } [[nodiscard]] ReturnT handle_call(const CallInfoT& call) { - static_assert(3 == std::variant_size_v, "Unexpected MatchResult alternative count."); - - std::vector noMatches{}; - std::vector exhaustedMatches{}; + std::vector> noMatches{}; + std::vector> inapplicableMatches{}; for (const std::scoped_lock lock{m_ExpectationsMx}; auto& exp : m_Expectations | std::views::reverse) { if (std::optional matchResult = detail::make_match_result(call, exp)) { - if (auto* match = std::get_if(&*matchResult)) + switch (*matchResult) { - report_ok( + using enum call::MatchResult; + case none: + noMatches.emplace_back(exp); + break; + case inapplicable: + inapplicableMatches.emplace_back(exp); + break; + case full: + detail::report_full_match( call, - std::move(*match)); + exp); exp->consume(call); return exp->finalize_call(call); - } - - if (auto* match = std::get_if(&*matchResult)) - { - noMatches.emplace_back(std::move(*match)); - } - else - { - exhaustedMatches.emplace_back(std::get(*std::move(matchResult))); + default: + unreachable(); } } } - detail::handle_call_match_fail( + if (!std::ranges::empty(inapplicableMatches)) + { + detail::report_inapplicable_matches( + call, + std::move(inapplicableMatches)); + } + + detail::report_no_matches( call, - std::move(noMatches), - std::move(exhaustedMatches)); + std::move(noMatches)); } private: @@ -265,7 +249,7 @@ namespace mimicpp } [[nodiscard]] - call::MatchResultT matches(const CallInfoT& call) const override + call::MatchResult matches(const CallInfoT& call) const override { if (!std::apply( [&](const auto&... policies) @@ -274,15 +258,15 @@ namespace mimicpp }, m_Policies)) { - return call::MatchResult_NoT{}; + return call::MatchResult::none; } if (!m_Times.is_applicable()) { - return call::MatchResult_NotApplicableT{}; + return call::MatchResult::inapplicable; } - return call::MatchResult_OkT{}; + return call::MatchResult::full; } constexpr void consume(const CallInfoT& call) override diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index 1c502d796..d4447822e 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -524,7 +524,7 @@ namespace mimicpp::expect * \note An expectation without requirements matches any call. * * \details Requirements are checked during the ``matches`` step. If all requirements match, an additional ``is_applicable`` check is - * performed on the times requirements. If this returns ``false``, the expectation is treated as ``non-applicable`` and will be skipped + * performed on the times requirements. If this returns ``false``, the expectation is treated as ``inapplicable`` and will be skipped * (but reported if no other match can be found). Otherwise, the call is matched. * *\{ diff --git a/include/mimic++/Fwd.hpp b/include/mimic++/Fwd.hpp index 9308c3048..d881deae6 100644 --- a/include/mimic++/Fwd.hpp +++ b/include/mimic++/Fwd.hpp @@ -13,6 +13,13 @@ namespace mimicpp::call { + enum class MatchResult + { + none, + inapplicable, + full + }; + enum class MatchCategory { no, diff --git a/include/mimic++/Utility.hpp b/include/mimic++/Utility.hpp index 298fa9971..cbdfcbe04 100644 --- a/include/mimic++/Utility.hpp +++ b/include/mimic++/Utility.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace mimicpp { @@ -147,16 +148,20 @@ namespace mimicpp using std::unreachable; #else - // taken from: https://en.cppreference.com/w/cpp/utility/unreachable + /** + * \brief Invokes undefined behavior + * \see https://en.cppreference.com/w/cpp/utility/unreachable + * \note Implementation directly taken from https://en.cppreference.com/w/cpp/utility/unreachable + */ [[noreturn]] inline void unreachable() { // Uses compiler specific extensions if possible. // Even if no extension is used, undefined behavior is still raised by // an empty function body and the noreturn attribute. - #if defined(_MSC_VER) && !defined(__clang__) // MSVC - __assume(false); - #else // GCC, Clang +#if defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else // GCC, Clang __builtin_unreachable(); #endif } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e2f350e25..f56325445 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable( "Matcher.cpp" "Mock.cpp" "Printer.cpp" + "Reporter.cpp" "Sequence.cpp" "TypeTraits.cpp" "Utility.cpp" diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 44682f173..e3ee5fa49 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -29,10 +29,9 @@ namespace { public: using CallInfoT = mimicpp::call::info_for_signature_t; - using MatchResultT = mimicpp::call::MatchResultT; MAKE_CONST_MOCK0(is_satisfied, bool(), noexcept override); - MAKE_CONST_MOCK1(matches, MatchResultT(const CallInfoT&), override); + MAKE_CONST_MOCK1(matches, mimicpp::call::MatchResult(const CallInfoT&), override); MAKE_MOCK1(consume, void(const CallInfoT&), override); MAKE_MOCK1(finalize_call, void(const CallInfoT&), override); }; @@ -106,15 +105,15 @@ TEST_CASE( REQUIRE_CALL(*expectations[3], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_CALL(*expectations[2], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_CALL(*expectations[1], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_OkT{}); + .RETURN(MatchResult::full); // expectations[3] is never queried REQUIRE_CALL(*expectations[1], consume(_)) .LR_WITH(&_1 == &call) @@ -135,20 +134,21 @@ TEST_CASE( Catch::Matchers::SizeIs(1)); } - SECTION("If at least one matches but is exhausted.") + SECTION("If at least one matches but is inapplicable.") { + using enum MatchResult; const auto [count, result0, result1, result2, result3] = GENERATE( - (table)( + (table)( { - {1u, MatchResult_NotApplicableT{}, MatchResult_NoT{}, MatchResult_NoT{}, MatchResult_NoT{}}, - {1u, MatchResult_NoT{}, MatchResult_NotApplicableT{}, MatchResult_NoT{}, MatchResult_NoT{}}, - {1u, MatchResult_NoT{}, MatchResult_NoT{}, MatchResult_NotApplicableT{}, MatchResult_NoT{}}, - {1u, MatchResult_NoT{}, MatchResult_NoT{}, MatchResult_NoT{}, MatchResult_NotApplicableT{}}, - - {2u, MatchResult_NotApplicableT{}, MatchResult_NoT{}, MatchResult_NotApplicableT{}, MatchResult_NoT{}}, - {2u, MatchResult_NoT{}, MatchResult_NotApplicableT{}, MatchResult_NoT{}, MatchResult_NotApplicableT{}}, - {3u, MatchResult_NotApplicableT{}, MatchResult_NoT{}, MatchResult_NotApplicableT{}, MatchResult_NotApplicableT{}}, - {4u, MatchResult_NotApplicableT{}, MatchResult_NotApplicableT{}, MatchResult_NotApplicableT{}, MatchResult_NotApplicableT{}} + {1u, inapplicable, none, none, none}, + {1u, none, inapplicable, none, none}, + {1u, none, none, inapplicable, none}, + {1u, none, none, none, inapplicable}, + + {2u, inapplicable, none, inapplicable, none}, + {2u, none, inapplicable, none, inapplicable}, + {3u, inapplicable, none, inapplicable, inapplicable}, + {4u, inapplicable, inapplicable, inapplicable, inapplicable} })); trompeloeil::sequence sequence{}; @@ -183,25 +183,25 @@ TEST_CASE( Catch::Matchers::IsEmpty()); } - SECTION("If all do not match.") + SECTION("If none matches.") { trompeloeil::sequence sequence{}; REQUIRE_CALL(*expectations[3], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_CALL(*expectations[2], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_CALL(*expectations[1], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_CALL(*expectations[0], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult_NoT{}); + .RETURN(MatchResult::none); REQUIRE_THROWS_AS( storage.handle_call(call), @@ -269,7 +269,7 @@ TEST_CASE( REQUIRE_CALL(*throwingExpectation, matches(_)) .THROW(Exception{}); REQUIRE_CALL(*otherExpectation, matches(_)) - .RETURN(MatchResult_OkT{}); + .RETURN(MatchResult::full); REQUIRE_CALL(*otherExpectation, consume(_)); REQUIRE_CALL(*otherExpectation, finalize_call(_)); REQUIRE_NOTHROW(storage.handle_call(call)); @@ -413,14 +413,14 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(true); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); } - SECTION("When times is not applicable => unapplicable match.") + SECTION("When times is not applicable => inapplicable.") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::inapplicable == std::as_const(expectation).matches(call)); } SECTION("Consume calls times.consume().") @@ -461,7 +461,7 @@ TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); } SECTION("When policy is matched.") @@ -473,17 +473,17 @@ TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); } - SECTION("And when times not applicable => unapplicable") + SECTION("And when times not applicable => inapplicable") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::inapplicable == std::as_const(expectation).matches(call)); } } @@ -524,7 +524,7 @@ TEMPLATE_TEST_CASE( }; REQUIRE(std::as_const(expectation).is_satisfied()); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); REQUIRE_NOTHROW(expectation.consume(call)); } @@ -547,7 +547,7 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); } SECTION("When not matched => no match") @@ -555,7 +555,7 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); } REQUIRE_CALL(policy, consume(_)) @@ -604,7 +604,7 @@ TEMPLATE_TEST_CASE( .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); } SECTION("When at least one not matches => no match") @@ -615,7 +615,7 @@ TEMPLATE_TEST_CASE( .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); } SECTION("When first matches, but second not.") @@ -627,7 +627,7 @@ TEMPLATE_TEST_CASE( .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(std::holds_alternative(std::as_const(expectation).matches(call))); + REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); } } From 4db7c9b19d5b83714db47599bf843c93a8a41bc2 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 16:44:51 +0200 Subject: [PATCH 11/68] refactor: completely overhaul match reporting --- include/mimic++/Call.hpp | 52 ------ include/mimic++/Expectation.hpp | 54 +++--- include/mimic++/Fwd.hpp | 37 +--- include/mimic++/Reporter.hpp | 261 ++++++++++++++++++++-------- test/Call.cpp | 29 ---- test/Expectation.cpp | 276 +++++++++++++++++------------- test/ExpectationBuilder.cpp | 6 +- test/Reporter.cpp | 21 +++ test/TestReporter.hpp | 293 ++++++++++++++------------------ test/TestTypes.hpp | 1 - 10 files changed, 544 insertions(+), 486 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index 985e8c6ac..2a05d76d7 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -80,58 +80,6 @@ namespace mimicpp::call }; } -template -struct std::formatter - : public std::formatter, Char> -{ - using MatchCategoryT = mimicpp::call::MatchCategory; - - auto format( - const MatchCategoryT category, - auto& ctx - ) const - { - constexpr auto toString = [](const MatchCategoryT cat) - { - switch (cat) - { - case MatchCategoryT::no: return "no match"; - case MatchCategoryT::non_applicable: return "non applicable match"; - case MatchCategoryT::ok: return "full match"; - } - - throw std::invalid_argument{"Unknown category value."}; - }; - - return std::formatter, Char>::format( - toString(category), - ctx); - } -}; - -namespace mimicpp::call -{ - class SubMatchResult - { - public: - bool matched{}; - - [[nodiscard]] - friend bool operator ==(const SubMatchResult&, const SubMatchResult&) = default; - }; - - template - class GenericMatchResult - : public std::integral_constant - { - public: - std::vector subMatchResults{}; - - [[nodiscard]] - friend bool operator ==(const GenericMatchResult&, const GenericMatchResult&) = default; - }; -} - namespace mimicpp::detail { template diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index d0de17d03..5c1bc7818 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -24,7 +24,7 @@ namespace mimicpp::detail { template - std::optional make_match_result( + std::optional make_match_report( const call::Info& call, const std::shared_ptr>& expectation ) noexcept @@ -68,7 +68,7 @@ namespace mimicpp virtual bool is_satisfied() const noexcept = 0; [[nodiscard]] - virtual call::MatchResult matches(const CallInfoT& call) const = 0; + virtual match_report matches(const CallInfoT& call) const = 0; virtual void consume(const CallInfoT& call) = 0; [[nodiscard]] @@ -124,27 +124,27 @@ namespace mimicpp [[nodiscard]] ReturnT handle_call(const CallInfoT& call) { - std::vector> noMatches{}; - std::vector> inapplicableMatches{}; + std::vector noMatches{}; + std::vector inapplicableMatches{}; for (const std::scoped_lock lock{m_ExpectationsMx}; auto& exp : m_Expectations | std::views::reverse) { - if (std::optional matchResult = detail::make_match_result(call, exp)) + if (std::optional matchReport = detail::make_match_report(call, exp)) { - switch (*matchResult) + switch (evaluate_match_report(*matchReport)) { - using enum call::MatchResult; + using enum MatchResult; case none: - noMatches.emplace_back(exp); + noMatches.emplace_back(*std::move(matchReport)); break; case inapplicable: - inapplicableMatches.emplace_back(exp); + inapplicableMatches.emplace_back(*std::move(matchReport)); break; case full: detail::report_full_match( call, - exp); + *std::move(matchReport)); exp->consume(call); return exp->finalize_call(call); default: @@ -249,24 +249,26 @@ namespace mimicpp } [[nodiscard]] - call::MatchResult matches(const CallInfoT& call) const override + match_report matches(const CallInfoT& call) const override { - if (!std::apply( - [&](const auto&... policies) - { - return (... && policies.matches(call)); + return match_report{ + .finalizeReport = std::nullopt, + .timesReport = match_report::times{ + .isApplicable = m_Times.is_applicable(), + .description = std::nullopt }, - m_Policies)) - { - return call::MatchResult::none; - } - - if (!m_Times.is_applicable()) - { - return call::MatchResult::inapplicable; - } - - return call::MatchResult::full; + .expectationReports = std::apply( + [&](const auto&... policies) + { + return std::vector{ + match_report::expectation{ + .matched = policies.matches(call), + .description = policies.describe() + }... + }; + }, + m_Policies) + }; } constexpr void consume(const CallInfoT& call) override diff --git a/include/mimic++/Fwd.hpp b/include/mimic++/Fwd.hpp index d881deae6..d68bd8f11 100644 --- a/include/mimic++/Fwd.hpp +++ b/include/mimic++/Fwd.hpp @@ -9,37 +9,9 @@ #pragma once #include -#include namespace mimicpp::call { - enum class MatchResult - { - none, - inapplicable, - full - }; - - enum class MatchCategory - { - no, - non_applicable, - ok - }; - - template - class GenericMatchResult; - - using MatchResult_NoT = GenericMatchResult; - using MatchResult_NotApplicableT = GenericMatchResult; - using MatchResult_OkT = GenericMatchResult; - - using MatchResultT = std::variant< - MatchResult_NoT, - MatchResult_NotApplicableT, - MatchResult_OkT - >; - template class Info; } @@ -49,6 +21,15 @@ namespace mimicpp template class Expectation; + enum class MatchResult + { + none, + inapplicable, + full + }; + + struct match_report; + using CharT = char; using CharTraitsT = std::char_traits; using StringT = std::basic_string; diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 21cfb9d87..2d684c57a 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -38,7 +38,7 @@ namespace mimicpp [[nodiscard]] friend bool operator ==(const call_report& lhs, const call_report& rhs) - { + { return lhs.returnTypeIndex == rhs.returnTypeIndex && lhs.argDetails == rhs.argDetails && is_same_source_location(lhs.fromLoc, rhs.fromLoc) @@ -47,7 +47,7 @@ namespace mimicpp } }; - template + template [[nodiscard]] call_report make_call_report(const call::Info& callInfo) { @@ -69,34 +69,149 @@ namespace mimicpp .fromConstness = callInfo.fromConstness }; } + + struct expectation_report + { + StringT description{}; + + [[nodiscard]] + friend bool operator==(const expectation_report&, const expectation_report&) = default; + }; + + template + [[nodiscard]] + expectation_report make_expectation_report(const Expectation& expectation) + { + return expectation_report{}; + } + + struct match_report + { + struct finalize + { + std::optional description{}; + + [[nodiscard]] + friend bool operator ==(const finalize&, const finalize&) = default; + }; + + struct times + { + bool isApplicable{}; + std::optional description{}; + + [[nodiscard]] + friend bool operator ==(const times&, const times&) = default; + }; + + struct expectation + { + bool matched{}; + std::optional description{}; + + [[nodiscard]] + friend bool operator ==(const expectation&, const expectation&) = default; + }; + + finalize finalizeReport{}; + times timesReport{}; + std::vector expectationReports{}; + + [[nodiscard]] + friend bool operator ==(const match_report&, const match_report&) = default; + }; + + [[nodiscard]] + inline MatchResult evaluate_match_report(const match_report& report) + { + if (!std::ranges::all_of(report.expectationReports, &match_report::expectation::matched)) + { + return MatchResult::none; + } + + if (!report.timesReport.isApplicable) + { + return MatchResult::inapplicable; + } + + return MatchResult::full; + } + + class IReporter + { + public: + virtual ~IReporter() = default; + [[noreturn]] - static void report_fail( - const call::Info& callInfo, - const std::vector& results - ) + virtual void report_no_matches( + call_report call, + std::vector matchReports + ) = 0; + + [[noreturn]] + virtual void report_inapplicable_matches( + call_report call, + std::vector matchReports + ) = 0; + + virtual void report_full_match( + call_report call, + match_report matchReport + ) noexcept = 0; + + virtual void report_unfulfilled_expectation( + expectation_report expectationReport + ) = 0; + + virtual void report_error(StringT message) = 0; + virtual void report_unhandled_exception( + call_report call, + expectation_report expectationReport, + std::exception_ptr exception + ) = 0; + + protected: + [[nodiscard]] + IReporter() = default; + + IReporter(const IReporter&) = default; + IReporter& operator =(const IReporter&) = default; + IReporter(IReporter&&) = default; + IReporter& operator =(IReporter&&) = default; + }; + + class DefaultReporter final + : public IReporter + { + public: + [[noreturn]] + void report_no_matches( + call_report call, + std::vector matchReports + ) override { std::terminate(); } - template [[noreturn]] - static void report_fail( - const call::Info& callInfo, - const std::vector& results - ) + void report_inapplicable_matches( + call_report call, + std::vector matchReports + ) override { std::terminate(); } - template - static constexpr void report_ok( - const call::Info& callInfo, - const call::MatchResult_OkT& result - ) + void report_full_match( + call_report call, + match_report matchReport + ) noexcept override { } - static void report_error(const StringT& message) + void report_unfulfilled_expectation( + expectation_report expectationReport + ) override { if (0 != std::uncaught_exceptions()) { @@ -104,80 +219,87 @@ namespace mimicpp } } - template - static void report_unhandled_exception( - const call::Info& callInfo, - std::shared_ptr> expectation, - std::exception_ptr exception - ) - { - } - - template - static constexpr void report_unsatisfied_expectation( - std::shared_ptr> expectation - ) + void report_error(StringT message) override { if (0 != std::uncaught_exceptions()) { std::terminate(); } } + + void report_unhandled_exception( + call_report call, + expectation_report expectationReport, + std::exception_ptr exception + ) override + { + } }; +} +namespace mimicpp::detail +{ [[nodiscard]] - constexpr auto create_reporter() noexcept + inline std::unique_ptr& get_reporter() noexcept { -#ifdef MIMICPP_CUSTOM_REPORTER - return MIMICPP_CUSTOM_REPORTER{}; -#else - return DefaultReporter{}; -#endif + static std::unique_ptr reporter{ + std::make_unique() + }; + return reporter; } template [[noreturn]] - void report_fail( + void report_no_matches( const call::Info& callInfo, - std::vector results + std::vector matchReports ) { - auto reporter = create_reporter(); - reporter.report_fail( - callInfo, - std::move(results)); + get_reporter() + ->report_no_matches( + make_call_report(callInfo), + std::move(matchReports)); } template [[noreturn]] - void report_fail( + void report_inapplicable_matches( const call::Info& callInfo, - std::vector results + std::vector matchReports ) { - auto reporter = create_reporter(); - reporter.report_fail( - callInfo, - std::move(results)); + get_reporter() + ->report_inapplicable_matches( + make_call_report(callInfo), + std::move(matchReports)); } template - void report_ok( + void report_full_match( const call::Info& callInfo, - call::MatchResult_OkT result + match_report matchReport + ) noexcept + { + get_reporter() + ->report_full_match( + make_call_report(callInfo), + std::move(matchReport)); + } + + template + void report_unfulfilled_expectation( + std::shared_ptr> expectation ) { - auto reporter = create_reporter(); - reporter.report_ok( - callInfo, - std::move(result)); + get_reporter() + ->report_unfulfilled_expectation( + make_expectation_report(*expectation)); } inline void report_error(StringT message) { - auto reporter = create_reporter(); - reporter.report_error( - std::move(message)); + get_reporter() + ->report_error(std::move(message)); } template @@ -187,21 +309,22 @@ namespace mimicpp std::exception_ptr exception ) { - auto reporter = create_reporter(); - reporter.report_unhandled_exception( - callInfo, - std::move(expectation), - std::move(exception)); + get_reporter() + ->report_unhandled_exception( + make_call_report(callInfo), + make_expectation_report(*expectation), + std::move(exception)); } +} - template - void report_unsatisfied_expectation( - std::shared_ptr> expectation - ) +namespace mimicpp +{ + template + requires std::constructible_from + void install_reporter(Args&&... args) { - auto reporter = create_reporter(); - reporter.report_unsatisfied_expectation( - std::move(expectation)); + detail::get_reporter() = std::make_unique( + std::forward(args)...); } } diff --git a/test/Call.cpp b/test/Call.cpp index 5b8389cf8..b07e73724 100644 --- a/test/Call.cpp +++ b/test/Call.cpp @@ -54,32 +54,3 @@ TEST_CASE( REQUIRE(expected == !(info != other)); REQUIRE(expected == !(other != info)); } - -TEST_CASE( - "call::MatchCategory is formattable.", - "[call]" -) -{ - namespace Matches = Catch::Matchers; - - SECTION("When valid category is given.") - { - const auto [expected, category] = GENERATE( - (table)({ - {"no match", call::MatchCategory::no}, - {"non applicable match", call::MatchCategory::non_applicable}, - {"full match", call::MatchCategory::ok}, - })); - - REQUIRE_THAT( - format::format("{}", category), - Matches::Equals(expected)); - } - - SECTION("When an invalid category is given, std::invalid_argument is thrown.") - { - REQUIRE_THROWS_AS( - format::format("{}", call::MatchCategory{42}), - std::invalid_argument); - } -} diff --git a/test/Expectation.cpp b/test/Expectation.cpp index e3ee5fa49..46cba28ef 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -20,7 +20,9 @@ #include #include #include +#include #include +#include namespace { @@ -31,14 +33,14 @@ namespace using CallInfoT = mimicpp::call::info_for_signature_t; MAKE_CONST_MOCK0(is_satisfied, bool(), noexcept override); - MAKE_CONST_MOCK1(matches, mimicpp::call::MatchResult(const CallInfoT&), override); + MAKE_CONST_MOCK1(matches, mimicpp::match_report(const CallInfoT&), override); MAKE_MOCK1(consume, void(const CallInfoT&), override); MAKE_MOCK1(finalize_call, void(const CallInfoT&), override); }; } TEST_CASE( - "mimicpp::ExpectationCollection collects expectations and reports when they are removed but unsatisfied.", + "mimicpp::ExpectationCollection collects expectations and reports when they are removed but unfulfilled.", "[expectation]" ) { @@ -49,33 +51,59 @@ TEST_CASE( REQUIRE_NOTHROW(storage.push(expectation)); - mimicpp::ScopedReporter reporter{}; + ScopedReporter reporter{}; SECTION("When expectation is satisfied, nothing is reported.") { REQUIRE_CALL(*expectation, is_satisfied()) .RETURN(true); REQUIRE_NOTHROW(storage.remove(expectation)); REQUIRE_THAT( - reporter.unsatisfied_expectations(), + reporter.unfulfilled_expectations(), Catch::Matchers::IsEmpty()); } - SECTION("When expectation is unsatisfied, get's reported.") + SECTION("When expectation is unfulfilled, get's reported.") { REQUIRE_CALL(*expectation, is_satisfied()) .RETURN(false); REQUIRE_NOTHROW(storage.remove(expectation)); REQUIRE_THAT( - reporter.unsatisfied_expectations(), + reporter.unfulfilled_expectations(), Catch::Matchers::SizeIs(1)); - auto reportedExpectation = std::any_cast>>( - reporter.unsatisfied_expectations().at(0)); - REQUIRE(expectation == reportedExpectation); + REQUIRE(make_expectation_report(*expectation) == reporter.unfulfilled_expectations().at(0)); } } +namespace +{ + inline const mimicpp::match_report commonNoMatchReport{ + .timesReport = { + .isApplicable = true + }, + .expectationReports = { + { + .matched = false + } + } + }; + + inline const mimicpp::match_report commonFullMatchReport{ + .timesReport = { + .isApplicable = true + }, + .expectationReports = {} + }; + + inline const mimicpp::match_report commonInapplicableMatchReport{ + .timesReport = { + .isApplicable = false + }, + .expectationReports = {} + }; +} + TEST_CASE( - "mimicpp::ExpectationCollection queries expectation, whether they match the call, in reverse order of construction.", + "mimicpp::ExpectationCollection queries its expectations, whether they match the call, in reverse order of construction.", "[expectation]" ) { @@ -84,7 +112,7 @@ TEST_CASE( using CallInfoT = Info; using trompeloeil::_; - mimicpp::ScopedReporter reporter{}; + ScopedReporter reporter{}; StorageT storage{}; std::vector> expectations(4); for (auto& exp : expectations) @@ -105,15 +133,15 @@ TEST_CASE( REQUIRE_CALL(*expectations[3], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_CALL(*expectations[2], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_CALL(*expectations[1], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::full); + .RETURN(commonFullMatchReport); // expectations[3] is never queried REQUIRE_CALL(*expectations[1], consume(_)) .LR_WITH(&_1 == &call) @@ -127,28 +155,29 @@ TEST_CASE( reporter.no_match_reports(), Catch::Matchers::IsEmpty()); REQUIRE_THAT( - reporter.non_applicable_match_reports(), + reporter.inapplicable_match_reports(), Catch::Matchers::IsEmpty()); REQUIRE_THAT( - reporter.ok_match_reports(), + reporter.full_match_reports(), Catch::Matchers::SizeIs(1)); } SECTION("If at least one matches but is inapplicable.") { - using enum MatchResult; + using match_report_t = mimicpp::match_report; const auto [count, result0, result1, result2, result3] = GENERATE( - (table)( + (table)( { - {1u, inapplicable, none, none, none}, - {1u, none, inapplicable, none, none}, - {1u, none, none, inapplicable, none}, - {1u, none, none, none, inapplicable}, - - {2u, inapplicable, none, inapplicable, none}, - {2u, none, inapplicable, none, inapplicable}, - {3u, inapplicable, none, inapplicable, inapplicable}, - {4u, inapplicable, inapplicable, inapplicable, inapplicable} + {1u, commonInapplicableMatchReport, commonNoMatchReport, commonNoMatchReport, commonNoMatchReport}, + {1u, commonNoMatchReport, commonInapplicableMatchReport, commonNoMatchReport, commonNoMatchReport}, + {1u, commonNoMatchReport, commonNoMatchReport, commonInapplicableMatchReport, commonNoMatchReport}, + {1u, commonNoMatchReport, commonNoMatchReport, commonNoMatchReport, commonInapplicableMatchReport}, + + {2u, commonInapplicableMatchReport, commonNoMatchReport, commonInapplicableMatchReport, commonNoMatchReport}, + {2u, commonNoMatchReport, commonInapplicableMatchReport, commonNoMatchReport, commonInapplicableMatchReport}, + {3u, commonInapplicableMatchReport, commonNoMatchReport, commonInapplicableMatchReport, commonInapplicableMatchReport}, + {4u, commonInapplicableMatchReport, commonInapplicableMatchReport, commonInapplicableMatchReport, + commonInapplicableMatchReport} })); trompeloeil::sequence sequence{}; @@ -176,10 +205,10 @@ TEST_CASE( reporter.no_match_reports(), Catch::Matchers::IsEmpty()); REQUIRE_THAT( - reporter.non_applicable_match_reports(), + reporter.inapplicable_match_reports(), Catch::Matchers::SizeIs(count)); REQUIRE_THAT( - reporter.ok_match_reports(), + reporter.full_match_reports(), Catch::Matchers::IsEmpty()); } @@ -189,19 +218,19 @@ TEST_CASE( REQUIRE_CALL(*expectations[3], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_CALL(*expectations[2], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_CALL(*expectations[1], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_CALL(*expectations[0], matches(_)) .LR_WITH(&_1 == &call) .IN_SEQUENCE(sequence) - .RETURN(MatchResult::none); + .RETURN(commonNoMatchReport); REQUIRE_THROWS_AS( storage.handle_call(call), @@ -210,10 +239,10 @@ TEST_CASE( reporter.no_match_reports(), Catch::Matchers::SizeIs(4)); REQUIRE_THAT( - reporter.non_applicable_match_reports(), + reporter.inapplicable_match_reports(), Catch::Matchers::IsEmpty()); REQUIRE_THAT( - reporter.ok_match_reports(), + reporter.full_match_reports(), Catch::Matchers::IsEmpty()); } } @@ -230,7 +259,7 @@ TEST_CASE( using CallInfoT = Info; using trompeloeil::_; - mimicpp::ScopedReporter reporter{}; + ScopedReporter reporter{}; StorageT storage{}; auto throwingExpectation = std::make_shared(); auto otherExpectation = std::make_shared(); @@ -247,7 +276,7 @@ TEST_CASE( { }; - const auto matches = [&](const unhandled_exception_info& info) + const auto matches = [&](const auto& info) { try { @@ -255,8 +284,8 @@ TEST_CASE( } catch (const Exception&) { - return call == std::any_cast(info.call) - && throwingExpectation == std::any_cast>>(info.expectation); + return info.call == mimicpp::make_call_report(call) + && info.expectation == make_expectation_report(*throwingExpectation); } catch (...) { @@ -264,24 +293,24 @@ TEST_CASE( } }; - SECTION("When thrown during matches.") + SECTION("When an exception is thrown during matches.") { REQUIRE_CALL(*throwingExpectation, matches(_)) .THROW(Exception{}); REQUIRE_CALL(*otherExpectation, matches(_)) - .RETURN(MatchResult::full); + .RETURN(commonFullMatchReport); REQUIRE_CALL(*otherExpectation, consume(_)); REQUIRE_CALL(*otherExpectation, finalize_call(_)); REQUIRE_NOTHROW(storage.handle_call(call)); CHECK_THAT( - reporter.ok_match_reports(), + reporter.full_match_reports(), Matches::SizeIs(1)); CHECK_THAT( reporter.no_match_reports(), Matches::IsEmpty()); CHECK_THAT( - reporter.non_applicable_match_reports(), + reporter.inapplicable_match_reports(), Matches::IsEmpty()); REQUIRE_THAT( @@ -328,53 +357,8 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == mimicpp::times_policy); } -namespace -{ - const std::array allSubMatchResultAlternatives = std::to_array( - { - {.matched = false}, - {.matched = true} - }); - - class CallMatchCategoryMatcher final - : public Catch::Matchers::MatcherGenericBase - { - public: - using CategoryT = mimicpp::call::MatchCategory; - - [[nodiscard]] - explicit CallMatchCategoryMatcher(const CategoryT category) noexcept - : m_Category{category} - { - } - - [[maybe_unused]] bool match(const auto& result) const - { - return m_Category == std::visit( - [](const auto& inner) { return inner.value; }, - result); - } - - std::string describe() const override - { - return mimicpp::format::format( - "matches category: {}", - m_Category); - } - - private: - CategoryT m_Category; - }; - - [[nodiscard]] - CallMatchCategoryMatcher matches_category(const mimicpp::call::MatchCategory category) noexcept - { - return CallMatchCategoryMatcher{category}; - } -} - TEST_CASE( - "Times policy of mimicpp::BasicExpectation controls, how often an expectations an expectation must be matched.", + "Times policy of mimicpp::BasicExpectation controls, how often its expectations must be matched.", "[expectation]" ) { @@ -385,6 +369,7 @@ TEST_CASE( using PolicyRefT = PolicyFacade>, UnwrapReferenceWrapper>; using TimesPolicyT = TimesFacade, UnwrapReferenceWrapper>; using CallInfoT = mimicpp::call::info_for_signature_t; + using TimesReportT = mimicpp::match_report::times; const CallInfoT call{ .args = {}, @@ -413,14 +398,18 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(true); - REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); } SECTION("When times is not applicable => inapplicable.") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); - REQUIRE(mimicpp::call::MatchResult::inapplicable == std::as_const(expectation).matches(call)); + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{false}); + REQUIRE(mimicpp::MatchResult::inapplicable == evaluate_match_report(matchReport)); } SECTION("Consume calls times.consume().") @@ -461,7 +450,12 @@ TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy, describe()) + .RETURN(mimicpp::StringT{}); + const bool isApplicable = GENERATE(false, true); + REQUIRE_CALL(times, is_applicable()) + .RETURN(isApplicable); + REQUIRE(mimicpp::MatchResult::none == evaluate_match_report(std::as_const(expectation).matches(call))); } SECTION("When policy is matched.") @@ -473,7 +467,9 @@ TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy, describe()) + .RETURN(mimicpp::StringT{}); + REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(std::as_const(expectation).matches(call))); } SECTION("And when times not applicable => inapplicable") @@ -483,7 +479,9 @@ TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(mimicpp::call::MatchResult::inapplicable == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy, describe()) + .RETURN(mimicpp::StringT{}); + REQUIRE(mimicpp::MatchResult::inapplicable == evaluate_match_report(std::as_const(expectation).matches(call))); } } @@ -509,6 +507,8 @@ TEMPLATE_TEST_CASE( using PolicyMockT = PolicyMock; using PolicyRefT = PolicyFacade>, UnwrapReferenceWrapper>; using CallInfoT = mimicpp::call::info_for_signature_t; + using ExpectationReportT = mimicpp::match_report::expectation; + using TimesReportT = mimicpp::match_report::times; const CallInfoT call{ .args = {}, @@ -524,7 +524,12 @@ TEMPLATE_TEST_CASE( }; REQUIRE(std::as_const(expectation).is_satisfied()); - REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE_THAT( + matchReport.expectationReports, + Catch::Matchers::IsEmpty()); + REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); REQUIRE_NOTHROW(expectation.consume(call)); } @@ -547,7 +552,14 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy, describe()) + .RETURN("policy description"); + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE_THAT( + matchReport.expectationReports, + Catch::Matchers::RangeEquals(std::vector{{true, "policy description"}})); + REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); } SECTION("When not matched => no match") @@ -555,7 +567,14 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy, matches(_)) .LR_WITH(&_1 == &call) .RETURN(false); - REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy, describe()) + .RETURN("policy description"); + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE_THAT( + matchReport.expectationReports, + Catch::Matchers::RangeEquals(std::vector{{false, "policy description"}})); + REQUIRE(mimicpp::MatchResult::none == evaluate_match_report(matchReport)); } REQUIRE_CALL(policy, consume(_)) @@ -600,35 +619,56 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy1, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); + REQUIRE_CALL(policy1, describe()) + .RETURN("policy1 description"); REQUIRE_CALL(policy2, matches(_)) .LR_WITH(&_1 == &call) .RETURN(true); - - REQUIRE(mimicpp::call::MatchResult::full == std::as_const(expectation).matches(call)); + REQUIRE_CALL(policy2, describe()) + .RETURN("policy2 description"); + + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE_THAT( + matchReport.expectationReports, + Catch::Matchers::UnorderedRangeEquals( + std::vector{ + {true, "policy1 description"}, + {true, "policy2 description"} + })); + REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); } SECTION("When at least one not matches => no match") { - SECTION("When first does not match, no other policy is checked.") - { - REQUIRE_CALL(policy1, matches(_)) - .LR_WITH(&_1 == &call) - .RETURN(false); - - REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); - } - - SECTION("When first matches, but second not.") - { - REQUIRE_CALL(policy1, matches(_)) - .LR_WITH(&_1 == &call) - .RETURN(true); - REQUIRE_CALL(policy2, matches(_)) - .LR_WITH(&_1 == &call) - .RETURN(false); + const auto [isMatching1, isMatching2] = GENERATE( + (table)({ + {false, true}, + {true, false}, + {false, false} + })); - REQUIRE(mimicpp::call::MatchResult::none == std::as_const(expectation).matches(call)); - } + REQUIRE_CALL(policy1, matches(_)) + .LR_WITH(&_1 == &call) + .RETURN(isMatching1); + REQUIRE_CALL(policy1, describe()) + .RETURN("policy1 description"); + REQUIRE_CALL(policy2, matches(_)) + .LR_WITH(&_1 == &call) + .RETURN(isMatching2); + REQUIRE_CALL(policy2, describe()) + .RETURN("policy2 description"); + + const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE_THAT( + matchReport.expectationReports, + Catch::Matchers::UnorderedRangeEquals( + std::vector{ + {isMatching1, "policy1 description"}, + {isMatching2, "policy2 description"} + })); + REQUIRE(mimicpp::MatchResult::none == evaluate_match_report(matchReport)); } SECTION("When calling consume()") diff --git a/test/ExpectationBuilder.cpp b/test/ExpectationBuilder.cpp index 7f01b3d49..e73143457 100644 --- a/test/ExpectationBuilder.cpp +++ b/test/ExpectationBuilder.cpp @@ -44,6 +44,8 @@ TEST_CASE( using ScopedExpectationT = ScopedExpectation; using CallInfoT = call::info_for_signature_t; + ScopedReporter reporter{}; + auto collection = std::make_shared>(); constexpr CallInfoT call{ .args = {}, @@ -97,7 +99,7 @@ TEST_CASE( REQUIRE_NOTHROW(collection->handle_call(call)); } - SECTION("And when times is saturated.") + SECTION("And when times is inapplicable.") { REQUIRE_CALL(times, is_applicable()) .RETURN(false); @@ -297,6 +299,6 @@ TEST_CASE( } REQUIRE_THAT( - reporter.unsatisfied_expectations(), + reporter.unfulfilled_expectations(), Catch::Matchers::IsEmpty()); } diff --git a/test/Reporter.cpp b/test/Reporter.cpp index 6748bde70..b20953ed8 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -212,3 +212,24 @@ TEST_CASE( }); } } + +TEST_CASE( + "expectation_report is equality comparable.", + "[reporting]" +) +{ + const expectation_report first{ + .description = "Hello, World!" + }; + + const auto [expectedEquality, second] = GENERATE( + (table({ + {false, {"not equal"}}, + {true, {"Hello, World!"}} + }))); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality == !(first != second)); + REQUIRE(expectedEquality == !(second!= first)); +} diff --git a/test/TestReporter.hpp b/test/TestReporter.hpp index 7a47f43c6..c2d0a5580 100644 --- a/test/TestReporter.hpp +++ b/test/TestReporter.hpp @@ -5,7 +5,7 @@ #pragma once -#include "mimic++/Fwd.hpp" +#include "mimic++/Reporter.hpp" #include #include @@ -21,188 +21,159 @@ class NonApplicableMatchError { }; -class TestReporter +class TestReporter final + : public mimicpp::IReporter { public: - template - [[noreturn]] - static void report_fail( - const mimicpp::call::Info& callInfo, - const std::vector& results - ); + using call_report_t = mimicpp::call_report; + using expectation_report_t = mimicpp::expectation_report; + using match_report_t = mimicpp::match_report; + + std::vector> noMatchResults{}; - template [[noreturn]] - static void report_fail( - const mimicpp::call::Info& callInfo, - const std::vector& results - ); - - template - static constexpr void report_ok( - const mimicpp::call::Info& callInfo, - const mimicpp::call::MatchResult_OkT& result - ); - - static void report_error(const mimicpp::StringT& message); - - template - static void report_unhandled_exception( - const mimicpp::call::Info& callInfo, - std::shared_ptr> expectation, - std::exception_ptr exception - ); + void report_no_matches( + call_report_t call, + std::vector matchReports + ) override + { + for (auto& exp : matchReports) + { + noMatchResults.emplace_back( + call, + std::move(exp)); + } - template - static constexpr void report_unsatisfied_expectation( - std::shared_ptr> expectation - ); -}; + throw NoMatchError{}; + } -#define MIMICPP_CUSTOM_REPORTER TestReporter + std::vector> inapplicableMatchResults{}; -#include "mimic++/Reporter.hpp" + [[noreturn]] + void report_inapplicable_matches( + call_report_t call, + std::vector matchReports + ) override + { + for (auto& exp : matchReports) + { + inapplicableMatchResults.emplace_back( + call, + std::move(exp)); + } -inline std::vector g_NoMatchResults{}; -inline std::vector g_NonApplicableMatchResults{}; -inline std::vector g_OkMatchResults{}; + throw NonApplicableMatchError{}; + } -struct unhandled_exception_info -{ - std::any call{}; - std::any expectation{}; - std::exception_ptr exception{}; -}; + std::vector> fullMatchResults{}; + + void report_full_match( + call_report_t call, + mimicpp::match_report matchReport + ) noexcept override + { + fullMatchResults.emplace_back( + std::move(call), + std::move(matchReport)); + } -inline std::vector g_UnhandledExceptions{}; -inline std::vector g_UnsatisfiedExpectations{}; -inline std::vector g_Errors{}; + std::vector unfulfilledExpectations{}; -template -void TestReporter::report_fail( - const mimicpp::call::Info& callInfo, - const std::vector& results -) -{ - g_NoMatchResults.insert( - std::ranges::end(g_NoMatchResults), - std::ranges::begin(results), - std::ranges::end(results)); - - throw NoMatchError{}; -} - -template -void TestReporter::report_fail( - const mimicpp::call::Info& callInfo, - const std::vector& results -) -{ - g_NonApplicableMatchResults.insert( - std::ranges::end(g_NonApplicableMatchResults), - std::ranges::begin(results), - std::ranges::end(results)); - - throw NonApplicableMatchError{}; -} - -template -constexpr void TestReporter::report_ok( - const mimicpp::call::Info& callInfo, - const mimicpp::call::MatchResult_OkT& result -) -{ - g_OkMatchResults.emplace_back(std::move(result)); -} + void report_unfulfilled_expectation( + expectation_report_t expectationReport + ) override + { + unfulfilledExpectations.emplace_back(std::move(expectationReport)); + } -inline void TestReporter::report_error(const mimicpp::StringT& message) -{ - g_Errors.emplace_back(std::move(message)); -} - -template -void TestReporter::report_unhandled_exception( - const mimicpp::call::Info& callInfo, - std::shared_ptr> expectation, - std::exception_ptr exception -) -{ - g_UnhandledExceptions.emplace_back( - unhandled_exception_info{ - .call = callInfo, - .expectation = std::move(expectation), - .exception = exception - }); -} - -template -constexpr void TestReporter::report_unsatisfied_expectation(std::shared_ptr> expectation) -{ - g_UnsatisfiedExpectations.emplace_back(std::move(expectation)); -} + std::vector errors{}; -namespace mimicpp -{ - class ScopedReporter + void report_error(mimicpp::StringT message) override { - public: - // ReSharper disable CppMemberFunctionMayBeStatic + errors.emplace_back(std::move(message)); + } - ~ScopedReporter() noexcept - { - clear(); - } + struct unhandled_exception_info + { + call_report_t call; + expectation_report_t expectation{}; + std::exception_ptr exception; + }; - ScopedReporter() noexcept - { - clear(); - } + std::vector unhandledExceptions{}; - void clear() - { - g_OkMatchResults.clear(); - g_NonApplicableMatchResults.clear(); - g_NoMatchResults.clear(); - g_Errors.clear(); - g_UnhandledExceptions.clear(); - g_UnsatisfiedExpectations.clear(); - } + void report_unhandled_exception( + call_report_t call, + expectation_report_t expectationReport, + std::exception_ptr exception + ) override + { + unhandledExceptions.emplace_back( + std::move(call), + std::move(expectationReport), + std::move(exception)); + } +}; - ScopedReporter(const ScopedReporter&) = delete; - ScopedReporter& operator =(const ScopedReporter&) = delete; - ScopedReporter(ScopedReporter&&) = delete; - ScopedReporter& operator =(ScopedReporter&&) = delete; +class ScopedReporter +{ +public: + ~ScopedReporter() noexcept + { + mimicpp::install_reporter(); + } - auto& no_match_reports() noexcept - { - return g_NoMatchResults; - } + ScopedReporter() noexcept + { + mimicpp::install_reporter(); + } - auto& non_applicable_match_reports() noexcept - { - return g_NonApplicableMatchResults; - } + ScopedReporter(const ScopedReporter&) = delete; + ScopedReporter& operator =(const ScopedReporter&) = delete; + ScopedReporter(ScopedReporter&&) = delete; + ScopedReporter& operator =(ScopedReporter&&) = delete; - auto& ok_match_reports() noexcept - { - return g_OkMatchResults; - } + auto& no_match_reports() noexcept + { + return reporter() + .noMatchResults; + } - auto& errors() noexcept - { - return g_Errors; - } + auto& inapplicable_match_reports() noexcept + { + return reporter() + .inapplicableMatchResults; + } - auto& unhandled_exceptions() noexcept - { - return g_UnhandledExceptions; - } + auto& full_match_reports() noexcept + { + return reporter() + .fullMatchResults; + } - auto& unsatisfied_expectations() noexcept - { - return g_UnsatisfiedExpectations; - } + auto& errors() noexcept + { + return reporter() + .errors; + } - // ReSharper restore CppMemberFunctionMayBeStatic - }; -} + auto& unhandled_exceptions() noexcept + { + return reporter() + .unhandledExceptions; + } + + auto& unfulfilled_expectations() noexcept + { + return reporter() + .unfulfilledExpectations; + } + +private: + [[nodiscard]] + static const TestReporter& reporter() + { + return dynamic_cast( + *mimicpp::detail::get_reporter()); + } +}; diff --git a/test/TestTypes.hpp b/test/TestTypes.hpp index 1f9464de8..2160a6c62 100644 --- a/test/TestTypes.hpp +++ b/test/TestTypes.hpp @@ -75,7 +75,6 @@ class PolicyFacade { public: using CallT = mimicpp::call::info_for_signature_t; - using SubMatchT = mimicpp::call::SubMatchResult; Policy policy; Projection projection; From 2084edff8436c3042d85bdb869005bc735c40a4f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 16:47:05 +0200 Subject: [PATCH 12/68] fix: strengthen install_reporter --- include/mimic++/Reporter.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 2d684c57a..8f22cc257 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -319,7 +319,7 @@ namespace mimicpp::detail namespace mimicpp { - template + template T, typename... Args> requires std::constructible_from void install_reporter(Args&&... args) { From 09e872c0344c2d00cb3b2763959d794c0a334b56 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 16:54:32 +0200 Subject: [PATCH 13/68] refactor: rename several report types to upper case --- include/mimic++/Expectation.hpp | 18 +++--- include/mimic++/Fwd.hpp | 4 +- include/mimic++/Reporter.hpp | 101 +++++++++++++++++--------------- test/Expectation.cpp | 33 +++++------ test/Reporter.cpp | 60 +++++++++---------- test/TestReporter.hpp | 12 ++-- 6 files changed, 118 insertions(+), 110 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 5c1bc7818..248cfec1e 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -24,7 +24,7 @@ namespace mimicpp::detail { template - std::optional make_match_report( + std::optional make_match_report( const call::Info& call, const std::shared_ptr>& expectation ) noexcept @@ -68,7 +68,7 @@ namespace mimicpp virtual bool is_satisfied() const noexcept = 0; [[nodiscard]] - virtual match_report matches(const CallInfoT& call) const = 0; + virtual MatchReport matches(const CallInfoT& call) const = 0; virtual void consume(const CallInfoT& call) = 0; [[nodiscard]] @@ -124,8 +124,8 @@ namespace mimicpp [[nodiscard]] ReturnT handle_call(const CallInfoT& call) { - std::vector noMatches{}; - std::vector inapplicableMatches{}; + std::vector noMatches{}; + std::vector inapplicableMatches{}; for (const std::scoped_lock lock{m_ExpectationsMx}; auto& exp : m_Expectations | std::views::reverse) @@ -249,19 +249,19 @@ namespace mimicpp } [[nodiscard]] - match_report matches(const CallInfoT& call) const override + MatchReport matches(const CallInfoT& call) const override { - return match_report{ + return MatchReport{ .finalizeReport = std::nullopt, - .timesReport = match_report::times{ + .timesReport = MatchReport::Times{ .isApplicable = m_Times.is_applicable(), .description = std::nullopt }, .expectationReports = std::apply( [&](const auto&... policies) { - return std::vector{ - match_report::expectation{ + return std::vector{ + MatchReport::Expectation{ .matched = policies.matches(call), .description = policies.describe() }... diff --git a/include/mimic++/Fwd.hpp b/include/mimic++/Fwd.hpp index d68bd8f11..67564bb17 100644 --- a/include/mimic++/Fwd.hpp +++ b/include/mimic++/Fwd.hpp @@ -28,7 +28,9 @@ namespace mimicpp full }; - struct match_report; + class CallReport; + class MatchReport; + class ExpectationReport; using CharT = char; using CharTraitsT = std::char_traits; diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 8f22cc257..bff449af6 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -19,25 +19,27 @@ namespace mimicpp { - struct call_report + class CallReport { - struct arg + public: + class Arg { + public: std::type_index typeIndex; StringT stateString; [[nodiscard]] - friend bool operator ==(const arg&, const arg&) = default; + friend bool operator ==(const Arg&, const Arg&) = default; }; std::type_index returnTypeIndex; - std::vector argDetails{}; + std::vector argDetails{}; std::source_location fromLoc{}; ValueCategory fromCategory{}; Constness fromConstness{}; [[nodiscard]] - friend bool operator ==(const call_report& lhs, const call_report& rhs) + friend bool operator ==(const CallReport& lhs, const CallReport& rhs) { return lhs.returnTypeIndex == rhs.returnTypeIndex && lhs.argDetails == rhs.argDetails @@ -49,15 +51,15 @@ namespace mimicpp template [[nodiscard]] - call_report make_call_report(const call::Info& callInfo) + CallReport make_call_report(const call::Info& callInfo) { - return call_report{ + return CallReport{ .returnTypeIndex = typeid(Return), .argDetails = std::apply( [](auto&... args) { - return std::vector{ - call_report::arg{ + return std::vector{ + CallReport::Arg{ .typeIndex = typeid(Params), .stateString = mimicpp::print(args.get()) }... @@ -70,61 +72,66 @@ namespace mimicpp }; } - struct expectation_report + class ExpectationReport { + public: StringT description{}; [[nodiscard]] - friend bool operator==(const expectation_report&, const expectation_report&) = default; + friend bool operator==(const ExpectationReport&, const ExpectationReport&) = default; }; template [[nodiscard]] - expectation_report make_expectation_report(const Expectation& expectation) + ExpectationReport make_expectation_report(const Expectation& expectation) { - return expectation_report{}; + return ExpectationReport{}; } - struct match_report + class MatchReport { - struct finalize + public: + class Finalize { + public: std::optional description{}; [[nodiscard]] - friend bool operator ==(const finalize&, const finalize&) = default; + friend bool operator ==(const Finalize&, const Finalize&) = default; }; - struct times + class Times { + public: bool isApplicable{}; std::optional description{}; [[nodiscard]] - friend bool operator ==(const times&, const times&) = default; + friend bool operator ==(const Times&, const Times&) = default; }; - struct expectation + class Expectation { + public: bool matched{}; std::optional description{}; [[nodiscard]] - friend bool operator ==(const expectation&, const expectation&) = default; + friend bool operator ==(const Expectation&, const Expectation&) = default; }; - finalize finalizeReport{}; - times timesReport{}; - std::vector expectationReports{}; + Finalize finalizeReport{}; + Times timesReport{}; + std::vector expectationReports{}; [[nodiscard]] - friend bool operator ==(const match_report&, const match_report&) = default; + friend bool operator ==(const MatchReport&, const MatchReport&) = default; }; [[nodiscard]] - inline MatchResult evaluate_match_report(const match_report& report) + inline MatchResult evaluate_match_report(const MatchReport& report) { - if (!std::ranges::all_of(report.expectationReports, &match_report::expectation::matched)) + if (!std::ranges::all_of(report.expectationReports, &MatchReport::Expectation::matched)) { return MatchResult::none; } @@ -144,29 +151,29 @@ namespace mimicpp [[noreturn]] virtual void report_no_matches( - call_report call, - std::vector matchReports + CallReport call, + std::vector matchReports ) = 0; [[noreturn]] virtual void report_inapplicable_matches( - call_report call, - std::vector matchReports + CallReport call, + std::vector matchReports ) = 0; virtual void report_full_match( - call_report call, - match_report matchReport + CallReport call, + MatchReport matchReport ) noexcept = 0; virtual void report_unfulfilled_expectation( - expectation_report expectationReport + ExpectationReport expectationReport ) = 0; virtual void report_error(StringT message) = 0; virtual void report_unhandled_exception( - call_report call, - expectation_report expectationReport, + CallReport call, + ExpectationReport expectationReport, std::exception_ptr exception ) = 0; @@ -186,8 +193,8 @@ namespace mimicpp public: [[noreturn]] void report_no_matches( - call_report call, - std::vector matchReports + CallReport call, + std::vector matchReports ) override { std::terminate(); @@ -195,22 +202,22 @@ namespace mimicpp [[noreturn]] void report_inapplicable_matches( - call_report call, - std::vector matchReports + CallReport call, + std::vector matchReports ) override { std::terminate(); } void report_full_match( - call_report call, - match_report matchReport + CallReport call, + MatchReport matchReport ) noexcept override { } void report_unfulfilled_expectation( - expectation_report expectationReport + ExpectationReport expectationReport ) override { if (0 != std::uncaught_exceptions()) @@ -228,8 +235,8 @@ namespace mimicpp } void report_unhandled_exception( - call_report call, - expectation_report expectationReport, + CallReport call, + ExpectationReport expectationReport, std::exception_ptr exception ) override { @@ -252,7 +259,7 @@ namespace mimicpp::detail [[noreturn]] void report_no_matches( const call::Info& callInfo, - std::vector matchReports + std::vector matchReports ) { get_reporter() @@ -265,7 +272,7 @@ namespace mimicpp::detail [[noreturn]] void report_inapplicable_matches( const call::Info& callInfo, - std::vector matchReports + std::vector matchReports ) { get_reporter() @@ -277,7 +284,7 @@ namespace mimicpp::detail template void report_full_match( const call::Info& callInfo, - match_report matchReport + MatchReport matchReport ) noexcept { get_reporter() diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 46cba28ef..c6a18e371 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -20,9 +20,8 @@ #include #include #include -#include -#include #include +#include namespace { @@ -33,7 +32,7 @@ namespace using CallInfoT = mimicpp::call::info_for_signature_t; MAKE_CONST_MOCK0(is_satisfied, bool(), noexcept override); - MAKE_CONST_MOCK1(matches, mimicpp::match_report(const CallInfoT&), override); + MAKE_CONST_MOCK1(matches, mimicpp::MatchReport(const CallInfoT&), override); MAKE_MOCK1(consume, void(const CallInfoT&), override); MAKE_MOCK1(finalize_call, void(const CallInfoT&), override); }; @@ -76,7 +75,7 @@ TEST_CASE( namespace { - inline const mimicpp::match_report commonNoMatchReport{ + inline const mimicpp::MatchReport commonNoMatchReport{ .timesReport = { .isApplicable = true }, @@ -87,14 +86,14 @@ namespace } }; - inline const mimicpp::match_report commonFullMatchReport{ + inline const mimicpp::MatchReport commonFullMatchReport{ .timesReport = { .isApplicable = true }, .expectationReports = {} }; - inline const mimicpp::match_report commonInapplicableMatchReport{ + inline const mimicpp::MatchReport commonInapplicableMatchReport{ .timesReport = { .isApplicable = false }, @@ -164,7 +163,7 @@ TEST_CASE( SECTION("If at least one matches but is inapplicable.") { - using match_report_t = mimicpp::match_report; + using match_report_t = mimicpp::MatchReport; const auto [count, result0, result1, result2, result3] = GENERATE( (table)( { @@ -369,7 +368,7 @@ TEST_CASE( using PolicyRefT = PolicyFacade>, UnwrapReferenceWrapper>; using TimesPolicyT = TimesFacade, UnwrapReferenceWrapper>; using CallInfoT = mimicpp::call::info_for_signature_t; - using TimesReportT = mimicpp::match_report::times; + using TimesReportT = mimicpp::MatchReport::Times; const CallInfoT call{ .args = {}, @@ -398,7 +397,7 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(true); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); } @@ -407,7 +406,7 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(false); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{false}); REQUIRE(mimicpp::MatchResult::inapplicable == evaluate_match_report(matchReport)); } @@ -507,8 +506,8 @@ TEMPLATE_TEST_CASE( using PolicyMockT = PolicyMock; using PolicyRefT = PolicyFacade>, UnwrapReferenceWrapper>; using CallInfoT = mimicpp::call::info_for_signature_t; - using ExpectationReportT = mimicpp::match_report::expectation; - using TimesReportT = mimicpp::match_report::times; + using ExpectationReportT = mimicpp::MatchReport::Expectation; + using TimesReportT = mimicpp::MatchReport::Times; const CallInfoT call{ .args = {}, @@ -524,7 +523,7 @@ TEMPLATE_TEST_CASE( }; REQUIRE(std::as_const(expectation).is_satisfied()); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE_THAT( matchReport.expectationReports, @@ -554,7 +553,7 @@ TEMPLATE_TEST_CASE( .RETURN(true); REQUIRE_CALL(policy, describe()) .RETURN("policy description"); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE_THAT( matchReport.expectationReports, @@ -569,7 +568,7 @@ TEMPLATE_TEST_CASE( .RETURN(false); REQUIRE_CALL(policy, describe()) .RETURN("policy description"); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE_THAT( matchReport.expectationReports, @@ -627,7 +626,7 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy2, describe()) .RETURN("policy2 description"); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE_THAT( matchReport.expectationReports, @@ -659,7 +658,7 @@ TEMPLATE_TEST_CASE( REQUIRE_CALL(policy2, describe()) .RETURN("policy2 description"); - const mimicpp::match_report matchReport = std::as_const(expectation).matches(call); + const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); REQUIRE(matchReport.timesReport == TimesReportT{true}); REQUIRE_THAT( matchReport.expectationReports, diff --git a/test/Reporter.cpp b/test/Reporter.cpp index b20953ed8..b09db601a 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -11,19 +11,19 @@ using namespace mimicpp; TEST_CASE( - "call_report::arg is equality comparable.", + "CallReport::Arg is equality comparable.", "[reporting]" ) { - using arg_t = call_report::arg; + using ArgT = CallReport::Arg; - const arg_t first{ + const ArgT first{ .typeIndex = typeid(int), .stateString = "42" }; const auto [expectedEquality, second] = GENERATE( - (table({ + (table({ {false, {typeid(int), "1337"}}, {false, {typeid(short), "42"}}, {true, {typeid(int), "42"}} @@ -36,11 +36,11 @@ TEST_CASE( } TEST_CASE( - "call_report is equality comparable.", + "CallReport is equality comparable.", "[reporting]" ) { - const call_report first{ + const CallReport first{ .returnTypeIndex = typeid(std::string), .argDetails = { { @@ -55,7 +55,7 @@ TEST_CASE( SECTION("When both sides are equal, they compare equal.") { - const call_report second{first}; + const CallReport second{first}; REQUIRE(first == second); REQUIRE(second == first); @@ -65,7 +65,7 @@ TEST_CASE( SECTION("When return type differs, they compare not equal.") { - call_report second{first}; + CallReport second{first}; second.returnTypeIndex = GENERATE(as{}, typeid(void), typeid(std::string_view)); @@ -77,7 +77,7 @@ TEST_CASE( SECTION("When category differs, they compare not equal.") { - call_report second{first}; + CallReport second{first}; second.fromCategory = GENERATE(ValueCategory::lvalue, ValueCategory::rvalue); @@ -89,7 +89,7 @@ TEST_CASE( SECTION("When constness differs, they compare not equal.") { - call_report second{first}; + CallReport second{first}; second.fromConstness = GENERATE(Constness::as_const, Constness::non_const); @@ -101,7 +101,7 @@ TEST_CASE( SECTION("When source location differs, they compare not equal.") { - call_report second{first}; + CallReport second{first}; second.fromLoc = std::source_location::current(); @@ -113,17 +113,17 @@ TEST_CASE( SECTION("When source location differs, they compare not equal.") { - call_report second{first}; + CallReport second{first}; - using arg_t = call_report::arg; + using ArgT = CallReport::Arg; second.argDetails = GENERATE( - std::vector{}, + std::vector{}, std::vector{ - (arg_t{.typeIndex = typeid(int), .stateString = "1337"}) + (ArgT{.typeIndex = typeid(int), .stateString = "1337"}) }, std::vector{ - (arg_t{.typeIndex = typeid(int), .stateString = "42"}), - (arg_t{.typeIndex = typeid(int), .stateString = "1337"}) + (ArgT{.typeIndex = typeid(int), .stateString = "42"}), + (ArgT{.typeIndex = typeid(int), .stateString = "1337"}) }); REQUIRE(first != second); @@ -147,11 +147,11 @@ TEST_CASE( .fromSourceLocation = std::source_location::current() }; - const call_report report = make_call_report(info); + const CallReport report = make_call_report(info); REQUIRE( report == - call_report{ + CallReport{ .returnTypeIndex = typeid(void), .argDetails = {}, .fromLoc = info.fromSourceLocation, @@ -169,11 +169,11 @@ TEST_CASE( .fromSourceLocation = std::source_location::current() }; - const call_report report = make_call_report(info); + const CallReport report = make_call_report(info); REQUIRE( report == - call_report{ + CallReport{ .returnTypeIndex = typeid(int), .argDetails = {}, .fromLoc = info.fromSourceLocation, @@ -194,17 +194,17 @@ TEST_CASE( .fromSourceLocation = std::source_location::current() }; - const call_report report = make_call_report(info); + const CallReport report = make_call_report(info); - using arg_t = call_report::arg; + using ArgT = CallReport::Arg; REQUIRE( report == - call_report{ + CallReport{ .returnTypeIndex = typeid(void), .argDetails = { - (arg_t{typeid(const int&), "1337"}), - (arg_t{typeid(double), "4.2"}), - (arg_t{typeid(std::string), "\"Hello, World!\""}) + (ArgT{typeid(const int&), "1337"}), + (ArgT{typeid(double), "4.2"}), + (ArgT{typeid(std::string), "\"Hello, World!\""}) }, .fromLoc = info.fromSourceLocation, .fromCategory = info.fromCategory, @@ -214,16 +214,16 @@ TEST_CASE( } TEST_CASE( - "expectation_report is equality comparable.", + "ExpectationReport is equality comparable.", "[reporting]" ) { - const expectation_report first{ + const ExpectationReport first{ .description = "Hello, World!" }; const auto [expectedEquality, second] = GENERATE( - (table({ + (table({ {false, {"not equal"}}, {true, {"Hello, World!"}} }))); diff --git a/test/TestReporter.hpp b/test/TestReporter.hpp index c2d0a5580..82c2119ef 100644 --- a/test/TestReporter.hpp +++ b/test/TestReporter.hpp @@ -25,16 +25,16 @@ class TestReporter final : public mimicpp::IReporter { public: - using call_report_t = mimicpp::call_report; - using expectation_report_t = mimicpp::expectation_report; - using match_report_t = mimicpp::match_report; + using call_report_t = mimicpp::CallReport; + using expectation_report_t = mimicpp::ExpectationReport; + using match_report_t = mimicpp::MatchReport; std::vector> noMatchResults{}; [[noreturn]] void report_no_matches( call_report_t call, - std::vector matchReports + std::vector matchReports ) override { for (auto& exp : matchReports) @@ -52,7 +52,7 @@ class TestReporter final [[noreturn]] void report_inapplicable_matches( call_report_t call, - std::vector matchReports + std::vector matchReports ) override { for (auto& exp : matchReports) @@ -69,7 +69,7 @@ class TestReporter final void report_full_match( call_report_t call, - mimicpp::match_report matchReport + mimicpp::MatchReport matchReport ) noexcept override { fullMatchResults.emplace_back( From 26d303b7cdde137090b55ddd0401afe53447ec54 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 16:59:48 +0200 Subject: [PATCH 14/68] cleanup: remove derived_cast function --- include/mimic++/Call.hpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index 2a05d76d7..199e1f896 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -80,17 +80,4 @@ namespace mimicpp::call }; } -namespace mimicpp::detail -{ - template - [[nodiscard]] - constexpr const Derived& derived_cast(const Base& self) noexcept - { - static_assert( - std::derived_from, - "Derived must inherit from Base."); - return static_cast(self); - } -} - #endif From 9f0b1f3a6e20141a847d5d88e9d10980aab633bb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 17:00:46 +0200 Subject: [PATCH 15/68] refactor: revisit some std includes --- include/mimic++/Call.hpp | 8 +------- include/mimic++/Expectation.hpp | 1 + include/mimic++/Reporter.hpp | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/include/mimic++/Call.hpp b/include/mimic++/Call.hpp index 199e1f896..3781c1b40 100644 --- a/include/mimic++/Call.hpp +++ b/include/mimic++/Call.hpp @@ -12,15 +12,9 @@ #include "mimic++/TypeTraits.hpp" #include "mimic++/Utility.hpp" -#include -#include -#include -#include #include -#include #include -#include -#include +#include namespace mimicpp::call::detail { diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 248cfec1e..0d4290b41 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index bff449af6..c570d0676 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -12,8 +12,10 @@ #include "mimic++/Fwd.hpp" #include "mimic++/Printer.hpp" +#include #include #include +#include #include #include From 5b734bea85879c0c6ff974deda7849914fe9e444 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 17:20:44 +0200 Subject: [PATCH 16/68] test: add test for severl reports --- include/mimic++/Expectation.hpp | 2 +- include/mimic++/Reporter.hpp | 4 +- test/Expectation.cpp | 2 +- test/Reporter.cpp | 151 +++++++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 7 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 0d4290b41..efe89548c 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -263,7 +263,7 @@ namespace mimicpp { return std::vector{ MatchReport::Expectation{ - .matched = policies.matches(call), + .isMatching = policies.matches(call), .description = policies.describe() }... }; diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index c570d0676..c490f85d8 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -115,7 +115,7 @@ namespace mimicpp class Expectation { public: - bool matched{}; + bool isMatching{}; std::optional description{}; [[nodiscard]] @@ -133,7 +133,7 @@ namespace mimicpp [[nodiscard]] inline MatchResult evaluate_match_report(const MatchReport& report) { - if (!std::ranges::all_of(report.expectationReports, &MatchReport::Expectation::matched)) + if (!std::ranges::all_of(report.expectationReports, &MatchReport::Expectation::isMatching)) { return MatchResult::none; } diff --git a/test/Expectation.cpp b/test/Expectation.cpp index c6a18e371..376d3b53a 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -81,7 +81,7 @@ namespace }, .expectationReports = { { - .matched = false + .isMatching = false } } }; diff --git a/test/Reporter.cpp b/test/Reporter.cpp index b09db601a..df68dea7d 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -202,9 +202,9 @@ TEST_CASE( CallReport{ .returnTypeIndex = typeid(void), .argDetails = { - (ArgT{typeid(const int&), "1337"}), - (ArgT{typeid(double), "4.2"}), - (ArgT{typeid(std::string), "\"Hello, World!\""}) + (ArgT{typeid(const int&), "1337"}), + (ArgT{typeid(double), "4.2"}), + (ArgT{typeid(std::string), "\"Hello, World!\""}) }, .fromLoc = info.fromSourceLocation, .fromCategory = info.fromCategory, @@ -233,3 +233,148 @@ TEST_CASE( REQUIRE(expectedEquality == !(first != second)); REQUIRE(expectedEquality == !(second!= first)); } + +TEST_CASE( + "MatchReport::Finalize is equality comparable." + "[reporting]" +) +{ + using ReportT = MatchReport::Finalize; + + const ReportT first{ + .description = "Hello, World!" + }; + + const auto [expectedEquality, second] = GENERATE( + (table({ + {false, {"not equal"}}, + {false, {std::nullopt}}, + {true, {"Hello, World!"}} + }))); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality == !(first != second)); + REQUIRE(expectedEquality == !(second!= first)); +} + +TEST_CASE( + "MatchReport::Times is equality comparable." + "[reporting]" +) +{ + using ReportT = MatchReport::Times; + + const ReportT first{ + .isApplicable = true, + .description = "Hello, World!" + }; + + const auto [expectedEquality, second] = GENERATE( + (table({ + {false, {true, "not equal"}}, + {false, {true, std::nullopt}}, + {false, {false, "Hello, World!"}}, + {true, {true, "Hello, World!"}} + }))); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality == !(first != second)); + REQUIRE(expectedEquality == !(second!= first)); +} + +TEST_CASE( + "MatchReport::Expectation is equality comparable." + "[reporting]" +) +{ + using ReportT = MatchReport::Expectation; + + const ReportT first{ + .isMatching = true, + .description = "Hello, World!" + }; + + const auto [expectedEquality, second] = GENERATE( + (table({ + {false, {true, "not equal"}}, + {false, {true, std::nullopt}}, + {false, {false, "Hello, World!"}}, + {true, {true, "Hello, World!"}} + }))); + + REQUIRE(expectedEquality == (first == second)); + REQUIRE(expectedEquality == (second == first)); + REQUIRE(expectedEquality == !(first != second)); + REQUIRE(expectedEquality == !(second!= first)); +} + +TEST_CASE( + "MatchReport is equality comparable.", + "[reporting]" +) +{ + const MatchReport first{ + .finalizeReport = {"finalize description"}, + .timesReport = {true, "times description"}, + .expectationReports = { + {true, "expectation description"} + } + }; + + SECTION("When both sides are equal, they compare equal.") + { + const MatchReport second{first}; + + REQUIRE(first == second); + REQUIRE(second == first); + REQUIRE(!(first != second)); + REQUIRE(!(second != first)); + } + + SECTION("When finalize report differs, they do not compare equal.") + { + MatchReport second{first}; + + second.finalizeReport = {"other finalize description"}; + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When times report differs, they do not compare equal.") + { + MatchReport second{first}; + + second.timesReport = {true, "other times description"}; + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } + + SECTION("When expectation reports differ, they do not compare equal.") + { + MatchReport second{first}; + + using ExpectationT = MatchReport::Expectation; + second.expectationReports = GENERATE( + std::vector{}, + std::vector{ + (ExpectationT{true, "other expectation description"}) + }, + std::vector{ + (ExpectationT{true, "expectation description"}), + (ExpectationT{false, "other expectation description"}) + }); + + REQUIRE(first != second); + REQUIRE(second != first); + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + } +} From 90ff2688ab7ec338c5bca1b7089b3c84c3d6f61a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 17:22:23 +0200 Subject: [PATCH 17/68] fix: explicitly utility unreachable() in noreturn functions --- include/mimic++/Reporter.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index c490f85d8..c1b6f9d96 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -268,6 +268,9 @@ namespace mimicpp::detail ->report_no_matches( make_call_report(callInfo), std::move(matchReports)); + + // ReSharper disable once CppUnreachableCode + unreachable(); } template @@ -281,6 +284,9 @@ namespace mimicpp::detail ->report_inapplicable_matches( make_call_report(callInfo), std::move(matchReports)); + + // ReSharper disable once CppUnreachableCode + unreachable(); } template @@ -330,7 +336,7 @@ namespace mimicpp { template T, typename... Args> requires std::constructible_from - void install_reporter(Args&&... args) + void install_reporter(Args&&... args) // NOLINT(cppcoreguidelines-missing-std-forward) { detail::get_reporter() = std::make_unique( std::forward(args)...); From a14125c1695c65bdf210ab951a5bf132e7e06f59 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 18:49:24 +0200 Subject: [PATCH 18/68] refactor: use report types in reporting functions --- include/mimic++/Expectation.hpp | 18 +++++++------- include/mimic++/Reporter.hpp | 44 ++++++++++++++------------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index efe89548c..b5003a8a0 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -27,18 +27,18 @@ namespace mimicpp::detail template std::optional make_match_report( const call::Info& call, - const std::shared_ptr>& expectation + const Expectation& expectation ) noexcept { try { - return expectation->matches(call); + return expectation.matches(call); } catch (...) { report_unhandled_exception( - call, - expectation, + make_call_report(call), + make_expectation_report(expectation), std::current_exception()); } @@ -118,7 +118,7 @@ namespace mimicpp if (!expectation->is_satisfied()) { detail::report_unfulfilled_expectation( - std::move(expectation)); + make_expectation_report(*expectation)); } } @@ -131,7 +131,7 @@ namespace mimicpp for (const std::scoped_lock lock{m_ExpectationsMx}; auto& exp : m_Expectations | std::views::reverse) { - if (std::optional matchReport = detail::make_match_report(call, exp)) + if (std::optional matchReport = detail::make_match_report(call, *exp)) { switch (evaluate_match_report(*matchReport)) { @@ -144,7 +144,7 @@ namespace mimicpp break; case full: detail::report_full_match( - call, + make_call_report(call), *std::move(matchReport)); exp->consume(call); return exp->finalize_call(call); @@ -157,12 +157,12 @@ namespace mimicpp if (!std::ranges::empty(inapplicableMatches)) { detail::report_inapplicable_matches( - call, + make_call_report(call), std::move(inapplicableMatches)); } detail::report_no_matches( - call, + make_call_report(call), std::move(noMatches)); } diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index c1b6f9d96..d05b8bf4a 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -257,58 +257,53 @@ namespace mimicpp::detail return reporter; } - template [[noreturn]] - void report_no_matches( - const call::Info& callInfo, + inline void report_no_matches( + CallReport callReport, std::vector matchReports ) { get_reporter() ->report_no_matches( - make_call_report(callInfo), + std::move(callReport), std::move(matchReports)); // ReSharper disable once CppUnreachableCode unreachable(); } - template [[noreturn]] - void report_inapplicable_matches( - const call::Info& callInfo, + inline void report_inapplicable_matches( + CallReport callReport, std::vector matchReports ) { get_reporter() ->report_inapplicable_matches( - make_call_report(callInfo), + std::move(callReport), std::move(matchReports)); // ReSharper disable once CppUnreachableCode unreachable(); } - template - void report_full_match( - const call::Info& callInfo, + inline void report_full_match( + CallReport callReport, MatchReport matchReport ) noexcept { get_reporter() ->report_full_match( - make_call_report(callInfo), + std::move(callReport), std::move(matchReport)); } - template - void report_unfulfilled_expectation( - std::shared_ptr> expectation + inline void report_unfulfilled_expectation( + ExpectationReport expectationReport ) { get_reporter() - ->report_unfulfilled_expectation( - make_expectation_report(*expectation)); + ->report_unfulfilled_expectation(std::move(expectationReport)); } inline void report_error(StringT message) @@ -317,18 +312,17 @@ namespace mimicpp::detail ->report_error(std::move(message)); } - template - void report_unhandled_exception( - const call::Info& callInfo, - std::shared_ptr> expectation, - std::exception_ptr exception + inline void report_unhandled_exception( + CallReport callReport, + ExpectationReport expectationReport, + const std::exception_ptr& exception ) { get_reporter() ->report_unhandled_exception( - make_call_report(callInfo), - make_expectation_report(*expectation), - std::move(exception)); + std::move(callReport), + std::move(expectationReport), + exception); } } From a2013917f5ab5a9bc24ec0715d82b3a7e54b63ec Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 18:49:54 +0200 Subject: [PATCH 19/68] test: add reporting tests --- test/Reporter.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/test/Reporter.cpp b/test/Reporter.cpp index df68dea7d..bae9728c6 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -6,6 +6,7 @@ #include "mimic++/Reporter.hpp" #include +#include #include using namespace mimicpp; @@ -378,3 +379,122 @@ TEST_CASE( REQUIRE(!(second == first)); } } + +namespace +{ + class ReporterMock + : public IReporter + { + public: + MAKE_MOCK2(report_no_matches, void(CallReport, std::vector), override); + MAKE_MOCK2(report_inapplicable_matches, void(CallReport, std::vector), override); + MAKE_MOCK2(report_full_match, void(CallReport, MatchReport), noexcept override); + MAKE_MOCK1(report_unfulfilled_expectation, void(ExpectationReport), override); + MAKE_MOCK1(report_error, void(StringT), override); + MAKE_MOCK3(report_unhandled_exception, void(CallReport, ExpectationReport, std::exception_ptr), override); + }; +} + +TEST_CASE( + "install_reporter removes the previous reporter and installs a new one.", + "[reporting]" +) +{ + install_reporter>(); + + { + auto& prevReporter = dynamic_cast&>(*detail::get_reporter()); + REQUIRE_DESTRUCTION(prevReporter); + install_reporter(); + } +} + +namespace +{ + class TestException + { + }; +} + +TEST_CASE( + "free report functions forward to the currently installed reporter.", + "[reporting]" +) +{ + install_reporter(); + auto& reporter = dynamic_cast(*detail::get_reporter()); + + const CallReport callReport{ + .returnTypeIndex = typeid(void), + .fromLoc = std::source_location::current() + }; + + const std::vector matchReports{ + {.finalizeReport = {"match1"}}, + {.finalizeReport = {"match2"}} + }; + + const ExpectationReport expectationReport{ + .description = "ExpectationReport" + }; + + SECTION("When report_no_matches() is called.") + { + REQUIRE_CALL(reporter, report_no_matches(callReport, matchReports)) + .THROW(TestException{}); + + REQUIRE_THROWS_AS( + detail::report_no_matches( + callReport, + matchReports), + TestException); + } + + SECTION("When report_inapplicable_matches() is called.") + { + REQUIRE_CALL(reporter, report_inapplicable_matches(callReport, matchReports)) + .THROW(TestException{}); + + REQUIRE_THROWS_AS( + detail::report_inapplicable_matches( + callReport, + matchReports), + TestException); + } + + SECTION("When report_full_match() is called.") + { + REQUIRE_CALL(reporter, report_full_match(callReport, matchReports.front())); + + detail::report_full_match( + callReport, + matchReports.front()); + } + + SECTION("When report_unfulfilled_expectation() is called.") + { + REQUIRE_CALL(reporter, report_unfulfilled_expectation(expectationReport)); + + detail::report_unfulfilled_expectation( + expectationReport); + } + + SECTION("When report_error() is called.") + { + const StringT error{"Error!"}; + REQUIRE_CALL(reporter, report_error(error)); + + detail::report_error(error); + } + + SECTION("When report_unhandled_exception() is called.") + { + const std::exception_ptr exception = std::make_exception_ptr(TestException{}); + REQUIRE_CALL(reporter, report_unhandled_exception(callReport, expectationReport, exception)); + + detail::report_unhandled_exception( + callReport, + expectationReport, + exception); + } +} From 826458b9a1dcf16932923d86cc377495aad22c7b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 19:12:27 +0200 Subject: [PATCH 20/68] test: add cases for evalue_match_report --- test/Reporter.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/Reporter.cpp b/test/Reporter.cpp index bae9728c6..dcff45d20 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -498,3 +498,50 @@ TEST_CASE( exception); } } + +TEST_CASE( + "evaluate_match_report determines the outcome of a match report.", + "[reporting]" +) +{ + using ExpectationReportT = MatchReport::Expectation; + + SECTION("When any policy doesn't match => MatchResult::none is returned.") + { + const MatchReport report{ + .timesReport = {GENERATE(true, false)}, + .expectationReports = GENERATE( + (std::vector{{false}}), + (std::vector{{true}, {false}}), + (std::vector{{false}, {true}})) + }; + + REQUIRE(MatchResult::none == evaluate_match_report(report)); + } + + SECTION("When all policy match but times is inapplicable => MatchResult::inapplicable is returned.") + { + const MatchReport report{ + .timesReport = {false}, + .expectationReports = GENERATE( + (std::vector{}), + (std::vector{{true}}), + (std::vector{{true}, {true}})) + }; + + REQUIRE(MatchResult::inapplicable == evaluate_match_report(report)); + } + + SECTION("When all policy match and times is applicable => MatchResult::full is returned.") + { + const MatchReport report{ + .timesReport = {true}, + .expectationReports = GENERATE( + (std::vector{}), + (std::vector{{true}}), + (std::vector{{true}, {true}})) + }; + + REQUIRE(MatchResult::full == evaluate_match_report(report)); + } +} From 52ad4c77c1aabe1ea321166cdca556bd0e01265d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 19:58:16 +0200 Subject: [PATCH 21/68] feat: add expectation_policies::Times and expectation_policies::RuntimeTimes describe_state --- include/mimic++/ExpectationPolicies.hpp | 93 +++++++++++++++++++++++++ test/ExpectationPolicies.cpp | 65 +++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index d4447822e..40eeca82c 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -16,6 +16,81 @@ #include #include +namespace mimicpp::expectation_policies::detail +{ + [[nodiscard]] + inline StringT describe_times_state(const std::size_t current, const std::size_t min, const std::size_t max) + { + const StringT currentDescription = std::invoke( + [&]() -> StringT + { + switch (current) + { + case 0: + return "never"; + case 1: + return "once"; + case 2: + return "twice"; + default: + return format::format("{} times", current); + } + }); + + if (current == max) + { + return format::format( + "inapplicable: already saturated (matched {})", + currentDescription); + } + + if (min <= current) + { + return format::format( + "applicable: accepts further matches (matched {} out of {} times)", + current, + max); + } + + const StringT intervalDescription = std::invoke( + [&]() -> StringT + { + if (min < max) + { + return format::format( + "between {} and {} times", + min, + max); + } + + return format::format( + "exactly {}", + currentDescription); + }); + + if (current == 0) + { + return format::format( + "unsatisfied: matched never - {} is expected", + current, + intervalDescription); + } + + if (current == 1) + { + return format::format( + "unsatisfied: matched just once - {} is expected", + current, + intervalDescription); + } + + return format::format( + "unsatisfied: matched just {} times - {} is expected", + current, + intervalDescription); + } +} + namespace mimicpp::expectation_policies { class InitFinalize @@ -45,6 +120,15 @@ namespace mimicpp::expectation_policies return m_Count < max; } + [[nodiscard]] + StringT describe_state() const + { + return detail::describe_times_state( + m_Count, + min, + max); + } + constexpr void consume() noexcept { ++m_Count; @@ -86,6 +170,15 @@ namespace mimicpp::expectation_policies return m_Count < m_Max; } + [[nodiscard]] + StringT describe_state() const + { + return detail::describe_times_state( + m_Count, + m_Min, + m_Max); + } + constexpr void consume() noexcept { ++m_Count; diff --git a/test/ExpectationPolicies.cpp b/test/ExpectationPolicies.cpp index 53fb7de6e..da6edcde1 100644 --- a/test/ExpectationPolicies.cpp +++ b/test/ExpectationPolicies.cpp @@ -170,6 +170,71 @@ TEST_CASE( std::runtime_error); } +TEST_CASE( + "both, expectation_policies::Times and expectation_policies::RuntimeTimes use detail::describe_times_state for their description.", + "[expectation][expectation::builder]" +) +{ + namespace Matches = Catch::Matchers; + + SECTION("When current == max.") + { + const auto [min, max] = GENERATE( + (table)({ + {0, 1}, + {0, 2}, + {3, 42}, + })); + + const auto description = expectation_policies::detail::describe_times_state( + max, + min, + max); + + REQUIRE_THAT( + description, + Matches::StartsWith("inapplicable: already saturated (matched ")); + } + + SECTION("When min <= current < max.") + { + const auto [current, min, max] = GENERATE( + (table)({ + {0, 0, 1}, + {1, 0, 2}, + {21, 3, 42}, + })); + + const auto description = expectation_policies::detail::describe_times_state( + current, + min, + max); + + REQUIRE_THAT( + description, + Matches::Matches("applicable: accepts further matches \\(matched \\d+ out of \\d+ times\\)")); + } + + SECTION("When current < min.") + { + const auto [current, min, max] = GENERATE( + (table)({ + {0, 1, 2}, + {1, 2, 5}, + {2, 3, 42}, + })); + + const auto description = expectation_policies::detail::describe_times_state( + current, + min, + max); + + REQUIRE_THAT( + description, + Matches::StartsWith("unsatisfied: matched ")); + } +} + TEMPLATE_TEST_CASE_SIG( "expectation_policies::Category checks whether the given call::Info matches.", "[expectation][expectation::policy]", From a2ef6b1e06fbacbe27cf225b5bfdb5711a4fc73c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 20:10:11 +0200 Subject: [PATCH 22/68] feat: add describe_state to expectation_policies::Sequence --- include/mimic++/Sequence.hpp | 16 ++++++++++++++++ test/Sequence.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/mimic++/Sequence.hpp b/include/mimic++/Sequence.hpp index 6a16d65f1..d43b7056c 100644 --- a/include/mimic++/Sequence.hpp +++ b/include/mimic++/Sequence.hpp @@ -234,6 +234,22 @@ namespace mimicpp::expectation_policies [](const entry& info){ return info.sequence->is_consumable(info.id); }); } + [[nodiscard]] + StringT describe_state() const + { + if (is_applicable()) + { + return "applicable: Sequence element expects further matches."; + } + + if (is_satisfied()) + { + return "inapplicable: Sequence element is already saturated."; + } + + return "inapplicable: Sequence element is not the current element."; + } + // ReSharper disable once CppMemberFunctionMayBeConst constexpr void consume() noexcept { diff --git a/test/Sequence.cpp b/test/Sequence.cpp index b933d9e73..8058b6938 100644 --- a/test/Sequence.cpp +++ b/test/Sequence.cpp @@ -225,6 +225,10 @@ TEST_CASE( Sequence sequence{}; + const StringT applicableText = "applicable: Sequence element expects further matches."; + const StringT saturatedText = "inapplicable: Sequence element is already saturated."; + const StringT inapplicableText = "inapplicable: Sequence element is not the current element."; + SECTION("When sequence contains just a single expectation.") { const auto count = GENERATE(range(1, 5)); @@ -234,11 +238,17 @@ TEST_CASE( { REQUIRE(!policy.is_satisfied()); REQUIRE(policy.is_applicable()); + REQUIRE_THAT( + policy.describe_state(), + Matches::Equals(applicableText)); REQUIRE_NOTHROW(policy.consume()); } REQUIRE(policy.is_satisfied()); REQUIRE(!policy.is_applicable()); + REQUIRE_THAT( + policy.describe_state(), + Matches::Equals(saturatedText)); } SECTION("When sequence has multiple expectations, the order matters.") @@ -251,9 +261,15 @@ TEST_CASE( { REQUIRE(!policy1.is_satisfied()); REQUIRE(policy1.is_applicable()); + REQUIRE_THAT( + policy1.describe_state(), + Matches::Equals(applicableText)); REQUIRE(!policy2.is_satisfied()); REQUIRE(!policy2.is_applicable()); + REQUIRE_THAT( + policy2.describe_state(), + Matches::Equals(inapplicableText)); REQUIRE_NOTHROW(policy1.consume()); @@ -261,18 +277,30 @@ TEST_CASE( { REQUIRE(policy1.is_satisfied()); REQUIRE(!policy1.is_applicable()); + REQUIRE_THAT( + policy1.describe_state(), + Matches::Equals(saturatedText)); REQUIRE(!policy2.is_satisfied()); REQUIRE(policy2.is_applicable()); + REQUIRE_THAT( + policy2.describe_state(), + Matches::Equals(applicableText)); REQUIRE_NOTHROW(policy2.consume()); } REQUIRE(policy1.is_satisfied()); REQUIRE(!policy1.is_applicable()); + REQUIRE_THAT( + policy1.describe_state(), + Matches::Equals(saturatedText)); REQUIRE(policy2.is_satisfied()); REQUIRE(!policy2.is_applicable()); + REQUIRE_THAT( + policy2.describe_state(), + Matches::Equals(saturatedText)); } } } From af171fa079e31352a498c4ac750f63a6b409f012 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 20:19:23 +0200 Subject: [PATCH 23/68] feat: collect times policies status text during handle_call --- include/mimic++/Expectation.hpp | 3 ++- test/Expectation.cpp | 14 ++++++++++++-- test/ExpectationBuilder.cpp | 4 ++++ test/TestTypes.hpp | 20 ++++++++++++++++++-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index b5003a8a0..a18e3d189 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -200,6 +200,7 @@ namespace mimicpp { { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to; { std::as_const(policy).is_applicable() } noexcept -> std::convertible_to; + { std::as_const(policy).describe_state() } -> std::convertible_to>; policy.consume(); }; @@ -256,7 +257,7 @@ namespace mimicpp .finalizeReport = std::nullopt, .timesReport = MatchReport::Times{ .isApplicable = m_Times.is_applicable(), - .description = std::nullopt + .description = m_Times.describe_state() }, .expectationReports = std::apply( [&](const auto&... policies) diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 376d3b53a..3c8b43af6 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -397,8 +397,10 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(true); + REQUIRE_CALL(times, describe_state()) + .RETURN("times state applicable"); const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); - REQUIRE(matchReport.timesReport == TimesReportT{true}); + REQUIRE(matchReport.timesReport == TimesReportT{true, "times state applicable"}); REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(matchReport)); } @@ -406,8 +408,10 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(false); + REQUIRE_CALL(times, describe_state()) + .RETURN("times state inapplicable"); const mimicpp::MatchReport matchReport = std::as_const(expectation).matches(call); - REQUIRE(matchReport.timesReport == TimesReportT{false}); + REQUIRE(matchReport.timesReport == TimesReportT{false, "times state inapplicable"}); REQUIRE(mimicpp::MatchResult::inapplicable == evaluate_match_report(matchReport)); } @@ -454,6 +458,8 @@ TEST_CASE( const bool isApplicable = GENERATE(false, true); REQUIRE_CALL(times, is_applicable()) .RETURN(isApplicable); + REQUIRE_CALL(times, describe_state()) + .RETURN(std::nullopt); REQUIRE(mimicpp::MatchResult::none == evaluate_match_report(std::as_const(expectation).matches(call))); } @@ -468,6 +474,8 @@ TEST_CASE( .RETURN(true); REQUIRE_CALL(policy, describe()) .RETURN(mimicpp::StringT{}); + REQUIRE_CALL(times, describe_state()) + .RETURN(std::nullopt); REQUIRE(mimicpp::MatchResult::full == evaluate_match_report(std::as_const(expectation).matches(call))); } @@ -480,6 +488,8 @@ TEST_CASE( .RETURN(true); REQUIRE_CALL(policy, describe()) .RETURN(mimicpp::StringT{}); + REQUIRE_CALL(times, describe_state()) + .RETURN(std::nullopt); REQUIRE(mimicpp::MatchResult::inapplicable == evaluate_match_report(std::as_const(expectation).matches(call))); } } diff --git a/test/ExpectationBuilder.cpp b/test/ExpectationBuilder.cpp index e73143457..a0c76e667 100644 --- a/test/ExpectationBuilder.cpp +++ b/test/ExpectationBuilder.cpp @@ -95,6 +95,8 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(true); + REQUIRE_CALL(times, describe_state()) + .RETURN(std::nullopt); REQUIRE_CALL(times, consume()); REQUIRE_NOTHROW(collection->handle_call(call)); } @@ -103,6 +105,8 @@ TEST_CASE( { REQUIRE_CALL(times, is_applicable()) .RETURN(false); + REQUIRE_CALL(times, describe_state()) + .RETURN(std::nullopt); REQUIRE_THROWS_AS( collection->handle_call(call), NonApplicableMatchError); diff --git a/test/TestTypes.hpp b/test/TestTypes.hpp index 2160a6c62..12371713c 100644 --- a/test/TestTypes.hpp +++ b/test/TestTypes.hpp @@ -179,6 +179,14 @@ class TimesFake return isApplicable; } + std::optional stateDescription{}; + + [[nodiscard]] + std::optional describe_state() const + { + return stateDescription; + } + static constexpr void consume() noexcept { } @@ -187,8 +195,9 @@ class TimesFake class TimesMock { public: - MAKE_CONST_MOCK0(is_satisfied, bool (), noexcept); - MAKE_CONST_MOCK0(is_applicable, bool (), noexcept); + MAKE_CONST_MOCK0(is_satisfied, bool(), noexcept); + MAKE_CONST_MOCK0(is_applicable, bool(), noexcept); + MAKE_CONST_MOCK0(describe_state, std::optional()); MAKE_MOCK0(consume, void ()); }; @@ -213,6 +222,13 @@ class TimesFacade .is_applicable(); } + [[nodiscard]] + std::optional describe_state() const + { + return std::invoke(projection, policy) + .describe_state(); + } + constexpr void consume() noexcept { return std::invoke(projection, policy) From af01cba4baf8b9a28ecf5005e8a794346348b145 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 20:21:46 +0200 Subject: [PATCH 24/68] mark unreachable() as excluded from coverage --- include/mimic++/Utility.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mimic++/Utility.hpp b/include/mimic++/Utility.hpp index cbdfcbe04..68a3fada3 100644 --- a/include/mimic++/Utility.hpp +++ b/include/mimic++/Utility.hpp @@ -144,6 +144,8 @@ namespace mimicpp return static_cast>(value); } + // GCOVR_EXCL_START + #ifdef __cpp_lib_unreachable using std::unreachable; #else @@ -166,6 +168,8 @@ namespace mimicpp #endif } #endif + + // GCOVR_EXCL_STOP } #endif From ab98df4282ccf7255ad9d89dcd75ea1121336c9d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 20:25:02 +0200 Subject: [PATCH 25/68] surround unreachable() with coverage exclusion markers --- include/mimic++/Reporter.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index d05b8bf4a..4226dee7e 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -268,8 +268,10 @@ namespace mimicpp::detail std::move(callReport), std::move(matchReports)); + // GCOVR_EXCL_START // ReSharper disable once CppUnreachableCode unreachable(); + // GCOVR_EXCL_STOP } [[noreturn]] @@ -283,8 +285,10 @@ namespace mimicpp::detail std::move(callReport), std::move(matchReports)); + // GCOVR_EXCL_START // ReSharper disable once CppUnreachableCode unreachable(); + // GCOVR_EXCL_STOP } inline void report_full_match( From 01829faa5b6a5c03be5e180782a56032f28249d9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 21:26:25 +0200 Subject: [PATCH 26/68] feat: let DefaultReporter throw instead of direct terminate --- include/mimic++/Reporter.hpp | 75 ++++++++++++++++++++-- test/Reporter.cpp | 117 +++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 4226dee7e..14b456c27 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -13,7 +13,9 @@ #include "mimic++/Printer.hpp" #include +#include #include +#include #include #include #include @@ -189,6 +191,43 @@ namespace mimicpp IReporter& operator =(IReporter&&) = default; }; + template + class Error final + : public std::runtime_error + { + public: + [[nodiscard]] + explicit Error( + const std::string& what, + Data&& data = Data{}, + const std::source_location& loc = std::source_location::current() + ) + : std::runtime_error{what}, + m_Data{std::move(data)}, + m_Loc{loc} + { + } + + [[nodiscard]] + const Data& data() const noexcept + { + return m_Data; + } + + [[nodiscard]] + const std::source_location& where() const noexcept + { + return m_Loc; + } + + private: + Data m_Data; + std::source_location m_Loc; + }; + + using UnmatchedCallT = Error>>; + using UnfulfilledExpectationT = Error; + class DefaultReporter final : public IReporter { @@ -199,7 +238,17 @@ namespace mimicpp std::vector matchReports ) override { - std::terminate(); + assert(std::ranges::all_of( + matchReports, + std::bind_front(std::equal_to{}, MatchResult::none), + &evaluate_match_report)); + + const std::source_location loc{call.fromLoc}; + throw UnmatchedCallT{ + "No match found.", + {std::move(call), std::move(matchReports)}, + loc + }; } [[noreturn]] @@ -208,7 +257,17 @@ namespace mimicpp std::vector matchReports ) override { - std::terminate(); + assert(std::ranges::all_of( + matchReports, + std::bind_front(std::equal_to{}, MatchResult::inapplicable), + &evaluate_match_report)); + + const std::source_location loc{call.fromLoc}; + throw UnmatchedCallT{ + "No applicable match found.", + {std::move(call), std::move(matchReports)}, + loc + }; } void report_full_match( @@ -216,23 +275,27 @@ namespace mimicpp MatchReport matchReport ) noexcept override { + assert(MatchResult::full == evaluate_match_report(matchReport)); } void report_unfulfilled_expectation( ExpectationReport expectationReport ) override { - if (0 != std::uncaught_exceptions()) + if (0 == std::uncaught_exceptions()) { - std::terminate(); + throw UnfulfilledExpectationT{ + "Expectation is unfulfilled.", + std::move(expectationReport) + }; } } void report_error(StringT message) override { - if (0 != std::uncaught_exceptions()) + if (0 == std::uncaught_exceptions()) { - std::terminate(); + throw Error{message}; } } diff --git a/test/Reporter.cpp b/test/Reporter.cpp index dcff45d20..33d7afbd8 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -545,3 +545,120 @@ TEST_CASE( REQUIRE(MatchResult::full == evaluate_match_report(report)); } } + +TEST_CASE( + "DefaultReporter throws exceptions on expectation violations.", + "[reporting]" +) +{ + DefaultReporter reporter{}; + + const CallReport callReport{ + .returnTypeIndex = typeid(void), + .fromLoc = std::source_location::current() + }; + + SECTION("When none matches are reported, UnmatchedCallT is thrown.") + { + REQUIRE_THROWS_AS( + reporter.report_no_matches( + callReport, + { + MatchReport{.timesReport = {true}, .expectationReports = {{false}} + } + }), + UnmatchedCallT); + } + + SECTION("When inapplicable matches are reported, UnmatchedCallT is thrown.") + { + REQUIRE_THROWS_AS( + reporter.report_inapplicable_matches( + callReport, + {MatchReport{.timesReport = {false}}}), + UnmatchedCallT); + } + + SECTION("When match is reported, nothing is done.") + { + REQUIRE_NOTHROW( + reporter.report_full_match( + callReport, + MatchReport{.timesReport = {true}})); + } + + SECTION("When unfulfilled expectation is reported.") + { + SECTION("And when there exists no uncaught exception, UnfulfilledExpectationT is thrown.") + { + REQUIRE_THROWS_AS( + reporter.report_unfulfilled_expectation({}), + UnfulfilledExpectationT); + } + + SECTION("And when there exists an uncaught exception, nothing is done.") + { + struct helper + { + ~helper() + { + rep.report_unfulfilled_expectation({}); + } + + DefaultReporter& rep; + }; + + const auto runTest = [&] + { + helper h{reporter}; + throw 42; + }; + + REQUIRE_THROWS_AS( + runTest(), + int); + } + } + + SECTION("When error is reported") + { + SECTION("And when there exists no uncaught exception, Error is thrown.") + { + REQUIRE_THROWS_AS( + reporter.report_error({"Test"}), + Error<>); + } + + SECTION("And when there exists an uncaught exception, nothing is done.") + { + struct helper + { + ~helper() + { + rep.report_error({"Test"}); + } + + DefaultReporter& rep; + }; + + const auto runTest = [&] + { + helper h{reporter}; + throw 42; + }; + + REQUIRE_THROWS_AS( + runTest(), + int); + } + } + + SECTION("When unhandled exception is reported, nothing is done.") + { + REQUIRE_NOTHROW( + reporter.report_unhandled_exception( + callReport, + {}, + std::make_exception_ptr(std::runtime_error{"Test"}))); + } +} From b5d10e7c8d4d90c6c375ee6baa64e49d9184a788 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 21:30:00 +0200 Subject: [PATCH 27/68] fix: several test cases, which actually reports unsatisfied expectations --- test/ExpectationBuilder.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/ExpectationBuilder.cpp b/test/ExpectationBuilder.cpp index a0c76e667..33f28f125 100644 --- a/test/ExpectationBuilder.cpp +++ b/test/ExpectationBuilder.cpp @@ -149,7 +149,7 @@ TEST_CASE( BaseBuilderT builder{ collection, - TimesFake{}, + TimesFake{.isSatisfied = true}, expectation_policies::InitFinalize{}, std::tuple{}}; @@ -205,7 +205,11 @@ TEST_CASE( std::reference_wrapper>, UnwrapReferenceWrapper>; FinalizerT finalizer{}; - ScopedExpectationT expectation = BaseBuilderT{collection, TimesFake{}, expectation_policies::InitFinalize{}, std::tuple{}} + ScopedExpectationT expectation = BaseBuilderT{ + collection, + TimesFake{.isSatisfied = true}, + expectation_policies::InitFinalize{}, + std::tuple{}} | FinalizerPolicyT{std::ref(finalizer)}; REQUIRE_CALL(finalizer, finalize_call(_)) From 5dda2a32ba28538287f8255c7bca8e5368def28a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 21:44:37 +0200 Subject: [PATCH 28/68] fix: apply code formatting on Reporter files --- include/mimic++/Reporter.hpp | 18 ++++++++++-------- test/Reporter.cpp | 8 ++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 14b456c27..f06a3dadc 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -238,10 +238,11 @@ namespace mimicpp std::vector matchReports ) override { - assert(std::ranges::all_of( - matchReports, - std::bind_front(std::equal_to{}, MatchResult::none), - &evaluate_match_report)); + assert( + std::ranges::all_of( + matchReports, + std::bind_front(std::equal_to{}, MatchResult::none), + &evaluate_match_report)); const std::source_location loc{call.fromLoc}; throw UnmatchedCallT{ @@ -257,10 +258,11 @@ namespace mimicpp std::vector matchReports ) override { - assert(std::ranges::all_of( - matchReports, - std::bind_front(std::equal_to{}, MatchResult::inapplicable), - &evaluate_match_report)); + assert( + std::ranges::all_of( + matchReports, + std::bind_front(std::equal_to{}, MatchResult::inapplicable), + &evaluate_match_report)); const std::source_location loc{call.fromLoc}; throw UnmatchedCallT{ diff --git a/test/Reporter.cpp b/test/Reporter.cpp index 33d7afbd8..2e6305a22 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -592,8 +592,8 @@ TEST_CASE( SECTION("And when there exists no uncaught exception, UnfulfilledExpectationT is thrown.") { REQUIRE_THROWS_AS( - reporter.report_unfulfilled_expectation({}), - UnfulfilledExpectationT); + reporter.report_unfulfilled_expectation({}), + UnfulfilledExpectationT); } SECTION("And when there exists an uncaught exception, nothing is done.") @@ -625,8 +625,8 @@ TEST_CASE( SECTION("And when there exists no uncaught exception, Error is thrown.") { REQUIRE_THROWS_AS( - reporter.report_error({"Test"}), - Error<>); + reporter.report_error({"Test"}), + Error<>); } SECTION("And when there exists an uncaught exception, nothing is done.") From aebbc8d39d3c0317011dab84161d24b7fc7f05ae Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sat, 4 May 2024 21:48:40 +0200 Subject: [PATCH 29/68] exclude unreachable branch from coverage report --- include/mimic++/Expectation.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index a18e3d189..f6d228662 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -148,8 +148,11 @@ namespace mimicpp *std::move(matchReport)); exp->consume(call); return exp->finalize_call(call); + + // GCOVR_EXCL_START default: unreachable(); + // GCOVR_EXCL_STOP } } } From aa4b97333ce3eaaa9ee176db0f205d14b0da8c78 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Sun, 5 May 2024 13:16:29 +0200 Subject: [PATCH 30/68] refactor: improve ValueCategory and Constness formatters --- include/mimic++/Utility.hpp | 32 ++++++++++---------- test/Utility.cpp | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/include/mimic++/Utility.hpp b/include/mimic++/Utility.hpp index 68a3fada3..c9993a77f 100644 --- a/include/mimic++/Utility.hpp +++ b/include/mimic++/Utility.hpp @@ -8,6 +8,8 @@ #pragma once +#include "mimic++/Fwd.hpp" + #include #include #include @@ -17,8 +19,8 @@ namespace mimicpp enum class Constness { non_const = 0b01, - as_const = 0b10, - any = non_const | as_const + as_const = 0b10, + any = non_const | as_const }; [[nodiscard]] @@ -44,8 +46,8 @@ namespace mimicpp } template <> -struct std::formatter - : public std::formatter +struct std::formatter + : public std::formatter { using ValueCategoryT = mimicpp::ValueCategory; @@ -63,18 +65,18 @@ struct std::formatter case ValueCategoryT::any: return "any"; } - throw std::runtime_error{"Unknown category value."}; + throw std::invalid_argument{"Unknown category value."}; }; - return std::formatter::format( + return std::formatter::format( toString(category), ctx); } }; template <> -struct std::formatter - : public std::formatter +struct std::formatter + : public std::formatter { using ConstnessT = mimicpp::Constness; @@ -92,10 +94,10 @@ struct std::formatter case ConstnessT::any: return "any"; } - throw std::runtime_error{"Unknown constness value."}; + throw std::invalid_argument{"Unknown constness value."}; }; - return std::formatter::format( + return std::formatter::format( toString(category), ctx); } @@ -103,7 +105,7 @@ struct std::formatter namespace mimicpp { - template + template struct always_false : public std::bool_constant { @@ -158,14 +160,14 @@ namespace mimicpp [[noreturn]] inline void unreachable() { - // Uses compiler specific extensions if possible. - // Even if no extension is used, undefined behavior is still raised by - // an empty function body and the noreturn attribute. + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. #if defined(_MSC_VER) && !defined(__clang__) // MSVC __assume(false); #else // GCC, Clang __builtin_unreachable(); - #endif +#endif } #endif diff --git a/test/Utility.cpp b/test/Utility.cpp index a03f7768b..a87e94a34 100644 --- a/test/Utility.cpp +++ b/test/Utility.cpp @@ -4,9 +4,11 @@ // // https://www.boost.org/LICENSE_1_0.txt) #include "mimic++/Utility.hpp" +#include "mimic++/Printer.hpp" #include #include +#include #include @@ -61,3 +63,61 @@ TEMPLATE_TEST_CASE( REQUIRE(value == to_underlying(Test{value})); } } + +TEST_CASE( + "ValueCategory is formattable.", + "[utility]" +) +{ + namespace Matches = Catch::Matchers; + + SECTION("When valid ValueCategory is given.") + { + const auto [expected, category] = GENERATE( + (table)({ + {"any", ValueCategory::any}, + {"rvalue",ValueCategory::rvalue}, + {"lvalue", ValueCategory::lvalue}, + })); + + REQUIRE_THAT( + format::format("{}", category), + Matches::Equals(expected)); + } + + SECTION("When an invalid ValueCategory is given, std::invalid_argument is thrown.") + { + REQUIRE_THROWS_AS( + format::format("{}", ValueCategory{42}), + std::invalid_argument); + } +} + +TEST_CASE( + "Constness is formattable.", + "[utility]" +) +{ + namespace Matches = Catch::Matchers; + + SECTION("When valid Constness is given.") + { + const auto [expected, category] = GENERATE( + (table)({ + {"any", Constness::any}, + {"const",Constness::as_const}, + {"mutable", Constness::non_const}, + })); + + REQUIRE_THAT( + format::format("{}", category), + Matches::Equals(expected)); + } + + SECTION("When an invalid Constness is given, std::invalid_argument is thrown.") + { + REQUIRE_THROWS_AS( + format::format("{}", Constness{42}), + std::invalid_argument); + } +} From eb6f9a727c86420f60cb9ac805423dc1e135d637 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 13:29:04 +0200 Subject: [PATCH 31/68] docs: add Reporter.hpp documentation --- include/mimic++/Reporter.hpp | 142 +++++++++++++++++++++++++++++++++++ test/Reporter.cpp | 3 +- 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index f06a3dadc..d05089a3a 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -23,6 +23,30 @@ namespace mimicpp { + /** + * \defgroup REPORTING reporting + * \brief Contains reporting related symbols + * \details Reports are simplified object representations of ``mimicpp`` types. In fact, reports are used to communicate with + * independent domains (e.g. unit-test frameworks) over the ``IReporter`` interface and are thus designed to provide as much + * transparent information as possible, without requiring them to be a generic type. + * + * At any time there exists exactly one global reporter, which may be directly or indirectly exchanged by users. + * Reports are sent to the currently installed reporter via the ``report_xyz`` free-functions. Most of those functions require, that + * reports are handled in a specific manner (e.g. ``report_no_matches`` is expected to never return) and custom reporters **must** + * follow that specification, otherwise this will lead to undefined behavior. For further details, have a look at the specific + * function documentation. + * + * \note In general users shall not directly interact with the installed reporter, except when they want to replace it. + * + *\{ + */ + + /** + * \brief Contains the extracted info from a typed ``call::Info``. + * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains + * the generic information as plain ``std`` types (e.g. the return type is provided as ``std::type_index`` instead of an actual + * type). + */ class CallReport { public: @@ -53,6 +77,15 @@ namespace mimicpp } }; + /** + * \brief Generates the call report for a given call info. + * \tparam Return The function return type. + * \tparam Params The function parameter types. + * \param callInfo The call info. + * \return The call report. + * \relatesalso CallReport + * \relatesalso call::Info + */ template [[nodiscard]] CallReport make_call_report(const call::Info& callInfo) @@ -76,6 +109,11 @@ namespace mimicpp }; } + /** + * \brief Contains the extracted info from a typed expectation. + * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains + * the generic information as plain ``std`` types. + */ class ExpectationReport { public: @@ -92,9 +130,17 @@ namespace mimicpp return ExpectationReport{}; } + /** + * \brief Contains the detailed information for match outcomes. + * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains + * the generic information as plain ``std`` types. + */ class MatchReport { public: + /** + * \brief Information about the used finalizer. + */ class Finalize { public: @@ -104,6 +150,12 @@ namespace mimicpp friend bool operator ==(const Finalize&, const Finalize&) = default; }; + /** + * \brief Information about the current times state. + * \details This type contains a description about the current state of the ``times`` policy. This description is gather + * in parallel to the ``matches`` (before the ``consume`` step) and thus contains more detailed information about the + * outcome. + */ class Times { public: @@ -114,6 +166,10 @@ namespace mimicpp friend bool operator ==(const Times&, const Times&) = default; }; + /** + * \brief Information a used expectation policy. + * \details This type contains a description about a given expectation policy. + */ class Expectation { public: @@ -132,6 +188,11 @@ namespace mimicpp friend bool operator ==(const MatchReport&, const MatchReport&) = default; }; + /** + * \brief Determines, whether a match report actually denotes a ``full``, ``inapplicable`` or ``no`` match. + * \param report The report to evaluate. + * \return The actual result. + */ [[nodiscard]] inline MatchResult evaluate_match_report(const MatchReport& report) { @@ -148,33 +209,99 @@ namespace mimicpp return MatchResult::full; } + /** + * \brief The reporter interface. + * \details This is the central interface to be used, when creating reporters for external domains. + */ class IReporter { public: + /** + * \brief Defaulted virtual destructor. + */ virtual ~IReporter() = default; + /** + * \brief Expects reports about all ``none`` matching expectations. This is only called, if there are no better options available. + * \param call The call report. + * \param matchReports Reports of all ``none`` matching expectations. + * \details This function is called, when no match has been found and there are no other expectations, which are matching but + * inapplicable. In fact, this is the fallback reporting mechanism, for unmatched calls. + * \note ``matchReports`` may be empty. + * + * \attention Derived reporter implementations must never return and shall instead leave the function via a thrown exception or + * a terminating mechanism (e.g. ``std::terminate``). Otherwise, this will result in undefined behavior. + */ [[noreturn]] virtual void report_no_matches( CallReport call, std::vector matchReports ) = 0; + /** + * \brief Expects reports about all ``inapplicable`` matching expectations. This is only called, if there are no better options available. + * \param call The call report. + * \param matchReports Reports of all ``inapplicable`` matching expectations. + * \details This function is called, when no applicable match has been found, but actually the call expectations are fulfilled. This in fact + * happens, when the ``times`` policy is already saturated (e.g. it was once expected and already matched once) or otherwise not applicable + * (e.g. a sequence element is not the current element). + * + * \attention Derived reporter implementations must never return and shall instead leave the function via a thrown exception or + * a terminating mechanism (e.g. ``std::terminate``). Otherwise, this will result in undefined behavior. + */ [[noreturn]] virtual void report_inapplicable_matches( CallReport call, std::vector matchReports ) = 0; + /** + * \brief Expects the report about a ``full`` matching expectation. + * \param call The call report. + * \param matchReport Report of the ``full`` matching expectation. + * \details This function is called, when a match has been found. There are no other expectations on the behavior of this function; + * except the ``noexcept`` guarantee. Implementations shall simply return to the caller. + */ virtual void report_full_match( CallReport call, MatchReport matchReport ) noexcept = 0; + /** + * \brief Expects the report of an unfulfilled expectation. + * \param expectationReport The expectation report. + * \details This function is called, when an unfulfilled expectation goes out of scope. In fact this happens, when the ``times`` policy is not + * satisfied. + * + * \note In general, it is expected that this function does not return, but throws an exception instead. But, as this function is always called + * when an unfulfilled expectation goes out of scope, implementations shall check whether an uncaught exception already exists (e.g. via + * ``std::uncaught_exceptions``) before throwing by themselves. + * \see ``DefaultReporter::report_unfulfilled_expectation`` for an example. + */ virtual void report_unfulfilled_expectation( ExpectationReport expectationReport ) = 0; + /** + * \brief Expects rather unspecific errors. + * \param message The error message. + * \details This function is called, when an unspecific error occurs. + * + * \note In general, it is expected that this function does not return, but throws an exception instead. But, as this function may be called + * due to any reason, implementations shall check whether an uncaught exception already exists (e.g. via ``std::uncaught_exceptions``) before + * throwing by themselves. + * \see ``DefaultReporter::report_error`` for an example. + */ virtual void report_error(StringT message) = 0; + + /** + * \brief Expects reports about unhandled exceptions, during ``handle_call``. + * \param call The call report. + * \param expectationReport The expectation report. + * \param exception The exception. + * \details This function is called, when an expectation throws during a ``matches`` call. There are no expectations on the behavior of this + * function. As this function is called inside a ``catch`` block, throwing exceptions will result in a terminate call. + */ virtual void report_unhandled_exception( CallReport call, ExpectationReport expectationReport, @@ -228,6 +355,9 @@ namespace mimicpp using UnmatchedCallT = Error>>; using UnfulfilledExpectationT = Error; + /** + * \brief The default reporter. + */ class DefaultReporter final : public IReporter { @@ -309,6 +439,10 @@ namespace mimicpp { } }; + + /** + * \} + */ } namespace mimicpp::detail @@ -397,6 +531,14 @@ namespace mimicpp::detail namespace mimicpp { + /** + * \brief Replaces the previous reporter with a newly constructed one. + * \tparam T The desired reporter type. + * \tparam Args The constructor argument types for ``T``. + * \param args The constructor arguments. + * \ingroup REPORTING + * \details This function accesses the globally available reporter and replaces it with a new instance. + */ template T, typename... Args> requires std::constructible_from void install_reporter(Args&&... args) // NOLINT(cppcoreguidelines-missing-std-forward) diff --git a/test/Reporter.cpp b/test/Reporter.cpp index 2e6305a22..b95b9beb1 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -564,8 +564,7 @@ TEST_CASE( reporter.report_no_matches( callReport, { - MatchReport{.timesReport = {true}, .expectationReports = {{false}} - } + MatchReport{.timesReport = {true}, .expectationReports = {{false}}} }), UnmatchedCallT); } From 29193512d92e7e600501bdb0cce4d0c3275fde09 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 13:39:03 +0200 Subject: [PATCH 32/68] remove non-returning calls from coverage report --- include/mimic++/Reporter.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index d05089a3a..1892fdb6b 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -463,7 +463,9 @@ namespace mimicpp::detail ) { get_reporter() + // GCOVR_EXCL_START ->report_no_matches( + // GCOVR_EXCL_STOP std::move(callReport), std::move(matchReports)); @@ -480,7 +482,9 @@ namespace mimicpp::detail ) { get_reporter() + // GCOVR_EXCL_START ->report_inapplicable_matches( + // GCOVR_EXCL_STOP std::move(callReport), std::move(matchReports)); From e2186ad3b3aec7a022b6ee1b276e7d313f79db6a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 13:40:23 +0200 Subject: [PATCH 33/68] fix: mark evaluate_match_report as noexcept --- include/mimic++/Reporter.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 1892fdb6b..985f35cdd 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -194,7 +194,7 @@ namespace mimicpp * \return The actual result. */ [[nodiscard]] - inline MatchResult evaluate_match_report(const MatchReport& report) + inline MatchResult evaluate_match_report(const MatchReport& report) noexcept { if (!std::ranges::all_of(report.expectationReports, &MatchReport::Expectation::isMatching)) { From 0e9e08ea24228021a9f176141c9b224d2f7a7abc Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 14:35:48 +0200 Subject: [PATCH 34/68] feat: stringify_match_report --- include/mimic++/Reporter.hpp | 79 +++++++++++++++++++++ test/Reporter.cpp | 129 +++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 985f35cdd..57897675a 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -209,6 +209,85 @@ namespace mimicpp return MatchResult::full; } + /** + * \brief Converts the given report to text. + * \param report The report. + * \return The report text. + */ + [[nodiscard]] + inline StringT stringify_match_report(const MatchReport& report) + { + std::vector matchedExpectationDescriptions{}; + std::vector unmatchedExpectationDescriptions{}; + + for (const auto& [isMatching, description] : report.expectationReports) + { + if (description) + { + if (isMatching) + { + matchedExpectationDescriptions.emplace_back(*description); + } + else + { + unmatchedExpectationDescriptions.emplace_back(*description); + } + } + } + + StringStreamT out{}; + + switch (evaluate_match_report(report)) + { + case MatchResult::full: + out << "Matched expectation:\n"; + break; + + case MatchResult::inapplicable: + format_to( + std::ostreambuf_iterator{out}, + "Inapplicable, but otherwise matched expectation:\n" + "reason: {}\n", + report.timesReport.description.value_or("No reason provided.")); + break; + + case MatchResult::none: + out << "Unmatched expectation:\n"; + break; + + // GCOVR_EXCL_START + default: // NOLINT(clang-diagnostic-covered-switch-default) + unreachable(); + // GCOVR_EXCL_STOP + } + + if (!std::ranges::empty(unmatchedExpectationDescriptions)) + { + out << "failed:\n"; + for (const auto& desc : unmatchedExpectationDescriptions) + { + format_to( + std::ostreambuf_iterator{out}, + "\t{},\n", + desc); + } + } + + if (!std::ranges::empty(matchedExpectationDescriptions)) + { + out << "passed:\n"; + for (const auto& desc : matchedExpectationDescriptions) + { + format_to( + std::ostreambuf_iterator{out}, + "\t{},\n", + desc); + } + } + + return std::move(out).str(); + } + /** * \brief The reporter interface. * \details This is the central interface to be used, when creating reporters for external domains. diff --git a/test/Reporter.cpp b/test/Reporter.cpp index b95b9beb1..fc8e219de 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace mimicpp; @@ -661,3 +662,131 @@ TEST_CASE( std::make_exception_ptr(std::runtime_error{"Test"}))); } } + +TEST_CASE( + "stringify_match_report converts the match report to text representation.", + "[report]" +) +{ + namespace Matches = Catch::Matchers; + + SECTION("When report denotes a full match.") + { + SECTION("Without any requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {true, "finalize description"}, + .expectationReports = {} + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Matched expectation:\n")); + } + + SECTION("When contains requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {true, "finalize description"}, + .expectationReports = { + {true, "Requirement1 description"}, + {true, "Requirement2 description"} + } + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Matched expectation:\n" + "passed:\n" + "\tRequirement1 description,\n" + "\tRequirement2 description,\n")); + } + } + + SECTION("When report denotes an inapplicable match.") + { + SECTION("Without any requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {false, "finalize description"}, + .expectationReports = {} + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Inapplicable, but otherwise matched expectation:\n" + "reason: finalize description\n")); + } + + SECTION("When contains requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {false, "finalize description"}, + .expectationReports = { + {true, "Requirement1 description"}, + {true, "Requirement2 description"} + } + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Inapplicable, but otherwise matched expectation:\n" + "reason: finalize description\n" + "passed:\n" + "\tRequirement1 description,\n" + "\tRequirement2 description,\n")); + } + } + + SECTION("When report denotes an unmatched report.") + { + SECTION("When contains only failed requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {true, "finalize description"}, + .expectationReports = { + {false, "Requirement1 description"}, + {false, "Requirement2 description"} + } + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Unmatched expectation:\n" + "failed:\n" + "\tRequirement1 description,\n" + "\tRequirement2 description,\n")); + } + + SECTION("When contains only mixed requirements.") + { + const MatchReport report{ + .finalizeReport = {}, + .timesReport = {true, "finalize description"}, + .expectationReports = { + {true, "Requirement1 description"}, + {false, "Requirement2 description"} + } + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Equals( + "Unmatched expectation:\n" + "failed:\n" + "\tRequirement2 description,\n" + "passed:\n" + "\tRequirement1 description,\n")); + } + } +} From a90f5ea05ca21f08375e03d7eb12f326dda7dd79 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 14:37:05 +0200 Subject: [PATCH 35/68] env: disable CppClangTidyHicppExceptionBaseclass check --- Folder.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/Folder.DotSettings b/Folder.DotSettings index 0767b94a8..5164e5d25 100644 --- a/Folder.DotSettings +++ b/Folder.DotSettings @@ -1,6 +1,7 @@  True True + DO_NOT_SHOW SUGGESTION SUGGESTION WARNING From 5ed65c2babfa3f8de5f7c0ddd90f7a4a9a546432 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 15:36:08 +0200 Subject: [PATCH 36/68] feat: stringify_call_report --- include/mimic++/Reporter.hpp | 49 ++++++++++++++++++++++++++++++++++ test/Reporter.cpp | 51 ++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 57897675a..c53982e0c 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -109,6 +110,53 @@ namespace mimicpp }; } + /** + * \brief Converts the given report to text. + * \param report The report. + * \return The report text. + * \relatesalso CallReport + */ + [[nodiscard]] + inline StringT stringify_call_report(const CallReport& report) + { + StringStreamT out{}; + format_to( + std::ostreambuf_iterator{out}, + "call from {}[{}:{}], {}\n", + report.fromLoc.file_name(), + report.fromLoc.line(), + report.fromLoc.column(), + report.fromLoc.function_name()); + + format_to( + std::ostreambuf_iterator{out}, + "constness: {}\n" + "value category: {}\n" + "return type: {}\n", + report.fromConstness, + report.fromCategory, + report.returnTypeIndex.name()); + + if (!std::ranges::empty(report.argDetails)) + { + out << "args:\n"; + for (const std::size_t i : std::views::iota(0u, std::ranges::size(report.argDetails))) + { + format_to( + std::ostreambuf_iterator{out}, + "\targ[{}]: {{\n" + "\t\ttype: {},\n" + "\t\tvalue: {}\n" + "\t}},\n", + i, + report.argDetails[i].typeIndex.name(), + report.argDetails[i].stateString); + } + } + + return std::move(out).str(); + } + /** * \brief Contains the extracted info from a typed expectation. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains @@ -213,6 +261,7 @@ namespace mimicpp * \brief Converts the given report to text. * \param report The report. * \return The report text. + * \relatesalso MatchReport */ [[nodiscard]] inline StringT stringify_match_report(const MatchReport& report) diff --git a/test/Reporter.cpp b/test/Reporter.cpp index fc8e219de..ab23a055f 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -790,3 +790,54 @@ TEST_CASE( } } } + +TEST_CASE( + "stringify_call_report converts the call report to text representation.", + "[report]" +) +{ + namespace Matches = Catch::Matchers; + + SECTION("When report without arguments is given.") + { + const CallReport report{ + .returnTypeIndex = typeid(void), + .argDetails = {}, + .fromLoc = std::source_location::current(), + .fromCategory = ValueCategory::any, + .fromConstness = Constness::any + }; + + REQUIRE_THAT( + stringify_call_report(report), + Matches::Matches( + "call from .+\\[\\d+:\\d+\\], .+\n" + "constness: any\n" + "value category: any\n" + "return type: void\n")); + } + + SECTION("When report with arguments is given.") + { + const CallReport report{ + .returnTypeIndex = typeid(int), + .argDetails = {{.typeIndex = typeid(double), .stateString = "4.2"}}, + .fromLoc = std::source_location::current(), + .fromCategory = ValueCategory::lvalue, + .fromConstness = Constness::as_const + }; + + REQUIRE_THAT( + stringify_call_report(report), + Matches::Matches( + "call from .+\\[\\d+:\\d+\\], .+\n" + "constness: const\n" + "value category: lvalue\n" + "return type: int\n" + "args:\n" + "\targ\\[0\\]: \\{\n" + "\t\ttype: double,\n" + "\t\tvalue: 4.2\n" + "\t\\},\n")); + } +} From ab3005f6af55544b5de97ad63ab4209c7c6a5a2a Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 15:41:50 +0200 Subject: [PATCH 37/68] fix: make type name regex more relaxed --- test/Reporter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Reporter.cpp b/test/Reporter.cpp index ab23a055f..6dcfa06f2 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -814,7 +814,7 @@ TEST_CASE( "call from .+\\[\\d+:\\d+\\], .+\n" "constness: any\n" "value category: any\n" - "return type: void\n")); + "return type: (v|void)\n")); } SECTION("When report with arguments is given.") @@ -833,10 +833,10 @@ TEST_CASE( "call from .+\\[\\d+:\\d+\\], .+\n" "constness: const\n" "value category: lvalue\n" - "return type: int\n" + "return type: (i|int)\n" "args:\n" "\targ\\[0\\]: \\{\n" - "\t\ttype: double,\n" + "\t\ttype: (d|double),\n" "\t\tvalue: 4.2\n" "\t\\},\n")); } From 49fe66448e9b678a714954530127e6f24034c77d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 16:09:31 +0200 Subject: [PATCH 38/68] feat: add Expectation::report function --- include/mimic++/Expectation.hpp | 24 +++++++++++- include/mimic++/Reporter.hpp | 11 ++---- test/Expectation.cpp | 17 +++++++- test/Reporter.cpp | 69 ++++++++++++++++++++++++++------- 4 files changed, 96 insertions(+), 25 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index f6d228662..4318a0870 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -38,7 +38,7 @@ namespace mimicpp::detail { report_unhandled_exception( make_call_report(call), - make_expectation_report(expectation), + expectation.report(), std::current_exception()); } @@ -65,6 +65,9 @@ namespace mimicpp Expectation(Expectation&&) = delete; Expectation& operator =(Expectation&&) = delete; + [[nodiscard]] + virtual ExpectationReport report() const = 0; + [[nodiscard]] virtual bool is_satisfied() const noexcept = 0; @@ -118,7 +121,7 @@ namespace mimicpp if (!expectation->is_satisfied()) { detail::report_unfulfilled_expectation( - make_expectation_report(*expectation)); + expectation->report()); } } @@ -241,6 +244,23 @@ namespace mimicpp { } + [[nodiscard]] + ExpectationReport report() const override + { + return ExpectationReport{ + .finalizerDescription = std::nullopt, + .timesDescription = m_Times.describe_state(), + .expectationDescriptions = std::apply( + [&](const auto&... policies) + { + return std::vector>{ + policies.describe()... + }; + }, + m_Policies) + }; + } + [[nodiscard]] constexpr bool is_satisfied() const noexcept override { diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index c53982e0c..3da846276 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -165,19 +165,14 @@ namespace mimicpp class ExpectationReport { public: - StringT description{}; + std::optional finalizerDescription{}; + std::optional timesDescription{}; + std::vector> expectationDescriptions{}; [[nodiscard]] friend bool operator==(const ExpectationReport&, const ExpectationReport&) = default; }; - template - [[nodiscard]] - ExpectationReport make_expectation_report(const Expectation& expectation) - { - return ExpectationReport{}; - } - /** * \brief Contains the detailed information for match outcomes. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 3c8b43af6..59be63931 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -31,6 +31,7 @@ namespace public: using CallInfoT = mimicpp::call::info_for_signature_t; + MAKE_CONST_MOCK0(report, mimicpp::ExpectationReport(), override); MAKE_CONST_MOCK0(is_satisfied, bool(), noexcept override); MAKE_CONST_MOCK1(matches, mimicpp::MatchReport(const CallInfoT&), override); MAKE_MOCK1(consume, void(const CallInfoT&), override); @@ -63,13 +64,19 @@ TEST_CASE( SECTION("When expectation is unfulfilled, get's reported.") { + const mimicpp::ExpectationReport expReport{ + .timesDescription = "times description" + }; + REQUIRE_CALL(*expectation, is_satisfied()) .RETURN(false); + REQUIRE_CALL(*expectation, report()) + .RETURN(expReport); REQUIRE_NOTHROW(storage.remove(expectation)); REQUIRE_THAT( reporter.unfulfilled_expectations(), Catch::Matchers::SizeIs(1)); - REQUIRE(make_expectation_report(*expectation) == reporter.unfulfilled_expectations().at(0)); + REQUIRE(expReport == reporter.unfulfilled_expectations().at(0)); } } @@ -275,6 +282,10 @@ TEST_CASE( { }; + const mimicpp::ExpectationReport throwingReport{ + .timesDescription = "times description" + }; + const auto matches = [&](const auto& info) { try @@ -284,7 +295,7 @@ TEST_CASE( catch (const Exception&) { return info.call == mimicpp::make_call_report(call) - && info.expectation == make_expectation_report(*throwingExpectation); + && info.expectation ==throwingReport; } catch (...) { @@ -296,6 +307,8 @@ TEST_CASE( { REQUIRE_CALL(*throwingExpectation, matches(_)) .THROW(Exception{}); + REQUIRE_CALL(*throwingExpectation, report()) + .RETURN(throwingReport); REQUIRE_CALL(*otherExpectation, matches(_)) .RETURN(commonFullMatchReport); REQUIRE_CALL(*otherExpectation, consume(_)); diff --git a/test/Reporter.cpp b/test/Reporter.cpp index 6dcfa06f2..d071b70d8 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -221,19 +221,64 @@ TEST_CASE( ) { const ExpectationReport first{ - .description = "Hello, World!" + .finalizerDescription = "finalizer description", + .timesDescription = "times description", + .expectationDescriptions = { + "first expectation description" + } }; - const auto [expectedEquality, second] = GENERATE( - (table({ - {false, {"not equal"}}, - {true, {"Hello, World!"}} - }))); + SECTION("When all members are equal, reports compare equal.") + { + const ExpectationReport second{first}; - REQUIRE(expectedEquality == (first == second)); - REQUIRE(expectedEquality == (second == first)); - REQUIRE(expectedEquality == !(first != second)); - REQUIRE(expectedEquality == !(second!= first)); + REQUIRE(first == second); + REQUIRE(second == first); + REQUIRE(!(first != second)); + REQUIRE(!(second!= first)); + } + + SECTION("When finalizer description differs, reports do not compare equal.") + { + ExpectationReport second{first}; + second.finalizerDescription = GENERATE( + as>{}, + std::nullopt, + "other finalizer description"); + + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + REQUIRE(first != second); + REQUIRE(second!= first); + } + + SECTION("When times description differs, reports do not compare equal.") + { + ExpectationReport second{first}; + second.timesDescription = GENERATE( + as>{}, + std::nullopt, + "other times description"); + + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + REQUIRE(first != second); + REQUIRE(second!= first); + } + + SECTION("When expectation descriptions differ, reports do not compare equal.") + { + ExpectationReport second{first}; + second.expectationDescriptions = GENERATE( + (std::vector>{}), + (std::vector>{"other expectation description"}), + (std::vector>{"expectation description", "other expectation description"})); + + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + REQUIRE(first != second); + REQUIRE(second!= first); + } } TEST_CASE( @@ -435,9 +480,7 @@ TEST_CASE( {.finalizeReport = {"match2"}} }; - const ExpectationReport expectationReport{ - .description = "ExpectationReport" - }; + const ExpectationReport expectationReport{}; SECTION("When report_no_matches() is called.") { From 9952e510fbfbe0e084f73ec920d7269c667a20fb Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 16:31:04 +0200 Subject: [PATCH 39/68] feat: stringify_expectation_report --- include/mimic++/Reporter.hpp | 46 ++++++++++++++++++ test/Reporter.cpp | 93 ++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 3da846276..195ff01de 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -173,6 +173,52 @@ namespace mimicpp friend bool operator==(const ExpectationReport&, const ExpectationReport&) = default; }; + /** + * \brief Converts the given report to text. + * \param report The report. + * \return The report text. + * \relatesalso ExpectationReport + */ + [[nodiscard]] + inline StringT stringify_expectation_report(const ExpectationReport& report) + { + StringStreamT out{}; + + out << "Expectation report:\n"; + + if (report.timesDescription) + { + format_to( + std::ostreambuf_iterator{out}, + "times: {}\n", + *report.timesDescription); + } + + if (std::ranges::any_of(report.expectationDescriptions, &std::optional::has_value)) + { + out << "expects:\n"; + for (const auto& desc + : report.expectationDescriptions + | std::views::filter(&std::optional::has_value)) + { + format_to( + std::ostreambuf_iterator{out}, + "\t{},\n", + *desc); + } + } + + if (report.finalizerDescription) + { + format_to( + std::ostreambuf_iterator{out}, + "finally: {}\n", + *report.finalizerDescription); + } + + return std::move(out).str(); + } + /** * \brief Contains the detailed information for match outcomes. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains diff --git a/test/Reporter.cpp b/test/Reporter.cpp index d071b70d8..386b3fef6 100644 --- a/test/Reporter.cpp +++ b/test/Reporter.cpp @@ -884,3 +884,96 @@ TEST_CASE( "\t\\},\n")); } } + +TEST_CASE( + "stringify_expectation_report converts the match report to text representation.", + "[report]" +) +{ + namespace Matches = Catch::Matchers; + + ExpectationReport report{ + .finalizerDescription = "finalizer description", + .timesDescription = "times description", + .expectationDescriptions = { + "expectation1 description" + } + }; + + SECTION("When full report is given.") + { + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "times: times description\n" + "expects:\n" + "\texpectation1 description,\n" + "finally: finalizer description\n")); + } + + SECTION("When times description is missing.") + { + report.timesDescription.reset(); + + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "expects:\n" + "\texpectation1 description,\n" + "finally: finalizer description\n")); + } + + SECTION("When finalizer description is missing.") + { + report.finalizerDescription.reset(); + + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "times: times description\n" + "expects:\n" + + "\texpectation1 description,\n")); + } + + SECTION("When expectation contains only empty descriptions.") + { + report.expectationDescriptions[0].reset(); + + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "times: times description\n" + "finally: finalizer description\n")); + } + + SECTION("When expectation contains no descriptions.") + { + report.expectationDescriptions.clear(); + + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "times: times description\n" + "finally: finalizer description\n")); + } + + SECTION("When expectation contains mixed descriptions.") + { + report.expectationDescriptions.emplace_back(std::nullopt); + + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Equals( + "Expectation report:\n" + "times: times description\n" + "expects:\n" + "\texpectation1 description,\n" + "finally: finalizer description\n")); + } +} From 4f18fad7836925e92e835628f05d67ba68a60764 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 16:37:10 +0200 Subject: [PATCH 40/68] fix: please clang --- include/mimic++/Expectation.hpp | 2 +- include/mimic++/Reporter.hpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 4318a0870..04625df0c 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -277,7 +277,7 @@ namespace mimicpp MatchReport matches(const CallInfoT& call) const override { return MatchReport{ - .finalizeReport = std::nullopt, + .finalizeReport = {std::nullopt}, .timesReport = MatchReport::Times{ .isApplicable = m_Times.is_applicable(), .description = m_Times.describe_state() diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 195ff01de..04d6726d5 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -194,12 +194,14 @@ namespace mimicpp *report.timesDescription); } - if (std::ranges::any_of(report.expectationDescriptions, &std::optional::has_value)) + if (std::ranges::any_of( + report.expectationDescriptions, + [](const auto& desc) { return desc.has_value(); })) { out << "expects:\n"; for (const auto& desc : report.expectationDescriptions - | std::views::filter(&std::optional::has_value)) + | std::views::filter([](const auto& desc) { return desc.has_value(); })) { format_to( std::ostreambuf_iterator{out}, From a717c6cf391fb285cc54fc83dbb20a7d94e2db82 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 16:53:09 +0200 Subject: [PATCH 41/68] test: add BasicExpectation::report test case --- test/Expectation.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 59be63931..2ea3d66c8 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -704,6 +704,76 @@ TEMPLATE_TEST_CASE( } } +TEST_CASE( + "mimicpp::BasicExpectation::report gathers information about its used policies.", + "[expectation]" +) +{ + namespace Matches = Catch::Matchers; + + using FinalizerPolicyT = FinalizerFake; + using TimesPolicyT = TimesFake; + + SECTION("Finalizer policy has no description.") + { + // Todo: + } + + SECTION("Times policy is queried.") + { + using TimesT = TimesFacade, UnwrapReferenceWrapper>; + + TimesMock times{}; + mimicpp::BasicExpectation< + void(), + TimesT, + FinalizerPolicyT> + expectation{ + TimesT{std::ref(times)}, + FinalizerPolicyT{} + }; + + REQUIRE_CALL(times, describe_state()) + .RETURN("times description"); + + const mimicpp::ExpectationReport report = expectation.report(); + REQUIRE_THAT( + *report.timesDescription, + Matches::Equals("times description")); + } + + SECTION("Expectation policies are queried.") + { + using PolicyT = PolicyFacade< + void(), + std::reference_wrapper>, + UnwrapReferenceWrapper>; + + PolicyMock policy{}; + mimicpp::BasicExpectation< + void(), + TimesPolicyT, + FinalizerPolicyT, + PolicyT> + expectation{ + TimesPolicyT{}, + FinalizerPolicyT{}, + PolicyT{std::ref(policy)} + }; + + REQUIRE_CALL(policy, describe()) + .RETURN("expectation description"); + + const mimicpp::ExpectationReport report = expectation.report(); + REQUIRE_THAT( + report.expectationDescriptions, + Matches::SizeIs(1)); + REQUIRE_THAT( + *report.expectationDescriptions[0], + Matches::Equals("expectation description")); + } +} + TEMPLATE_TEST_CASE( "mimicpp::BasicExpectation finalizer can be exchanged.", "[expectation]", From a44583bb6d6dc1d99afa48da8a06e2b2f6c96c77 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 17:05:46 +0200 Subject: [PATCH 42/68] test: extend BasicExpectation::report test case --- test/Expectation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Expectation.cpp b/test/Expectation.cpp index 2ea3d66c8..609acf898 100644 --- a/test/Expectation.cpp +++ b/test/Expectation.cpp @@ -737,6 +737,7 @@ TEST_CASE( .RETURN("times description"); const mimicpp::ExpectationReport report = expectation.report(); + REQUIRE(report.timesDescription); REQUIRE_THAT( *report.timesDescription, Matches::Equals("times description")); @@ -768,6 +769,7 @@ TEST_CASE( REQUIRE_THAT( report.expectationDescriptions, Matches::SizeIs(1)); + REQUIRE(report.expectationDescriptions[0]); REQUIRE_THAT( *report.expectationDescriptions[0], Matches::Equals("expectation description")); From 892f111ba256ad14e3e1029227bcea31f39706e8 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 17:17:16 +0200 Subject: [PATCH 43/68] env: move unit tests into unit-tests subfolder --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 6 +- examples/Finalizers.cpp | 2 - examples/Requirements.cpp | 2 - examples/Sequences.cpp | 2 - examples/SideEffects.cpp | 2 - test/CMakeLists.txt | 62 +------------------ test/unit-tests/CMakeLists.txt | 61 ++++++++++++++++++ test/{ => unit-tests}/Call.cpp | 0 test/{ => unit-tests}/ComplexMock.cpp | 0 test/{ => unit-tests}/Expectation.cpp | 0 test/{ => unit-tests}/ExpectationBuilder.cpp | 0 test/{ => unit-tests}/ExpectationPolicies.cpp | 0 test/{ => unit-tests}/Matcher.cpp | 0 test/{ => unit-tests}/Mock.cpp | 0 test/{ => unit-tests}/Printer.cpp | 0 test/{ => unit-tests}/Reporter.cpp | 0 test/{ => unit-tests}/Sequence.cpp | 0 test/{ => unit-tests}/TestReporter.hpp | 0 test/{ => unit-tests}/TestTypes.hpp | 0 test/{ => unit-tests}/TypeTraits.cpp | 0 test/{ => unit-tests}/Utility.cpp | 0 22 files changed, 66 insertions(+), 73 deletions(-) create mode 100644 test/unit-tests/CMakeLists.txt rename test/{ => unit-tests}/Call.cpp (100%) rename test/{ => unit-tests}/ComplexMock.cpp (100%) rename test/{ => unit-tests}/Expectation.cpp (100%) rename test/{ => unit-tests}/ExpectationBuilder.cpp (100%) rename test/{ => unit-tests}/ExpectationPolicies.cpp (100%) rename test/{ => unit-tests}/Matcher.cpp (100%) rename test/{ => unit-tests}/Mock.cpp (100%) rename test/{ => unit-tests}/Printer.cpp (100%) rename test/{ => unit-tests}/Reporter.cpp (100%) rename test/{ => unit-tests}/Sequence.cpp (100%) rename test/{ => unit-tests}/TestReporter.hpp (100%) rename test/{ => unit-tests}/TestTypes.hpp (100%) rename test/{ => unit-tests}/TypeTraits.cpp (100%) rename test/{ => unit-tests}/Utility.cpp (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b05aaf07..661d991f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -186,7 +186,7 @@ jobs: shell: bash env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir build/test -C ${{ matrix.build_mode }} -j4 + run: ctest --test-dir build/test/unit-tests -C ${{ matrix.build_mode }} -j4 - name: Run examples shell: bash diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2043bd68d..45bf5ada7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -42,7 +42,7 @@ jobs: - name: Run tests env: CTEST_OUTPUT_ON_FAILURE: 1 - run: ctest --test-dir build/test -C Debug -j4 + run: ctest --test-dir build/test/unit-tests -C Debug -j4 - name: Run examples env: @@ -53,7 +53,7 @@ jobs: run: | # circumvnet "fatal: detected dubious ownership in repository" error git config --global --add safe.directory /__w/mimicpp/mimicpp - gcovr --root build/test --filter "include/mimic++" --keep -j4 -v \ + gcovr --root build/test/unit-tests --filter "include/mimic++" --keep -j4 -v \ --exclude-lines-by-pattern "\s*assert\(" \ --exclude-unreachable-branches \ --exclude-noncode-lines \ @@ -68,7 +68,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: gcov-files - path: "build/test/*.gcov" + path: "build/test/unit-tests/*.gcov" - name: Upload generated report artifacts uses: actions/upload-artifact@v4 diff --git a/examples/Finalizers.cpp b/examples/Finalizers.cpp index b2ce54f4e..3a2532279 100644 --- a/examples/Finalizers.cpp +++ b/examples/Finalizers.cpp @@ -5,8 +5,6 @@ #include "mimic++/Mock.hpp" -#include "../test/TestReporter.hpp" - #include #include diff --git a/examples/Requirements.cpp b/examples/Requirements.cpp index 8a3306221..ab593de86 100644 --- a/examples/Requirements.cpp +++ b/examples/Requirements.cpp @@ -5,8 +5,6 @@ #include "mimic++/Mock.hpp" -#include "../test/TestReporter.hpp" - #include TEST_CASE( diff --git a/examples/Sequences.cpp b/examples/Sequences.cpp index fc71adb88..fccf889dd 100644 --- a/examples/Sequences.cpp +++ b/examples/Sequences.cpp @@ -6,8 +6,6 @@ #include "mimic++/Mock.hpp" #include "mimic++/Sequence.hpp" -#include "../test/TestReporter.hpp" - #include TEST_CASE( diff --git a/examples/SideEffects.cpp b/examples/SideEffects.cpp index dfccfb736..1c90d90bd 100644 --- a/examples/SideEffects.cpp +++ b/examples/SideEffects.cpp @@ -5,8 +5,6 @@ #include "mimic++/Mock.hpp" -#include "../test/TestReporter.hpp" - #include TEST_CASE( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f56325445..7fa162dff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,61 +1 @@ -CPMAddPackage("gh:catchorg/Catch2@3.5.4") -CPMAddPackage("gh:rollbear/trompeloeil@47") -include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") - -add_executable( - mimicpp-tests - "Call.cpp" - #"ComplexMock.cpp" - "Expectation.cpp" - "ExpectationBuilder.cpp" - "ExpectationPolicies.cpp" - "Matcher.cpp" - "Mock.cpp" - "Printer.cpp" - "Reporter.cpp" - "Sequence.cpp" - "TypeTraits.cpp" - "Utility.cpp" -) - -if (MSVC) - - # When using github ci, exceptions seems to be disabled by default. - target_compile_options( - mimicpp-tests - PRIVATE - /EHsc - ) - -endif() - -if (SANITIZE_ADDRESS) - - # workaround linker errors on msvc - # see: https://learn.microsoft.com/en-us/answers/questions/864574/enabling-address-sanitizer-results-in-error-lnk203 - target_compile_definitions( - mimicpp-tests - PRIVATE - $<$:_DISABLE_VECTOR_ANNOTATION> - $<$:_DISABLE_STRING_ANNOTATION> - ) - -endif() - -add_sanitizers(mimicpp-tests) - -target_link_libraries( - mimicpp-tests - PRIVATE - mimicpp::mimicpp - Catch2::Catch2WithMain - trompeloeil::trompeloeil -) - -target_compile_features( - mimicpp - INTERFACE - cxx_std_${CMAKE_CXX_STANDARD} -) - -catch_discover_tests(mimicpp-tests) +add_subdirectory("unit-tests") \ No newline at end of file diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt new file mode 100644 index 000000000..f56325445 --- /dev/null +++ b/test/unit-tests/CMakeLists.txt @@ -0,0 +1,61 @@ +CPMAddPackage("gh:catchorg/Catch2@3.5.4") +CPMAddPackage("gh:rollbear/trompeloeil@47") +include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") + +add_executable( + mimicpp-tests + "Call.cpp" + #"ComplexMock.cpp" + "Expectation.cpp" + "ExpectationBuilder.cpp" + "ExpectationPolicies.cpp" + "Matcher.cpp" + "Mock.cpp" + "Printer.cpp" + "Reporter.cpp" + "Sequence.cpp" + "TypeTraits.cpp" + "Utility.cpp" +) + +if (MSVC) + + # When using github ci, exceptions seems to be disabled by default. + target_compile_options( + mimicpp-tests + PRIVATE + /EHsc + ) + +endif() + +if (SANITIZE_ADDRESS) + + # workaround linker errors on msvc + # see: https://learn.microsoft.com/en-us/answers/questions/864574/enabling-address-sanitizer-results-in-error-lnk203 + target_compile_definitions( + mimicpp-tests + PRIVATE + $<$:_DISABLE_VECTOR_ANNOTATION> + $<$:_DISABLE_STRING_ANNOTATION> + ) + +endif() + +add_sanitizers(mimicpp-tests) + +target_link_libraries( + mimicpp-tests + PRIVATE + mimicpp::mimicpp + Catch2::Catch2WithMain + trompeloeil::trompeloeil +) + +target_compile_features( + mimicpp + INTERFACE + cxx_std_${CMAKE_CXX_STANDARD} +) + +catch_discover_tests(mimicpp-tests) diff --git a/test/Call.cpp b/test/unit-tests/Call.cpp similarity index 100% rename from test/Call.cpp rename to test/unit-tests/Call.cpp diff --git a/test/ComplexMock.cpp b/test/unit-tests/ComplexMock.cpp similarity index 100% rename from test/ComplexMock.cpp rename to test/unit-tests/ComplexMock.cpp diff --git a/test/Expectation.cpp b/test/unit-tests/Expectation.cpp similarity index 100% rename from test/Expectation.cpp rename to test/unit-tests/Expectation.cpp diff --git a/test/ExpectationBuilder.cpp b/test/unit-tests/ExpectationBuilder.cpp similarity index 100% rename from test/ExpectationBuilder.cpp rename to test/unit-tests/ExpectationBuilder.cpp diff --git a/test/ExpectationPolicies.cpp b/test/unit-tests/ExpectationPolicies.cpp similarity index 100% rename from test/ExpectationPolicies.cpp rename to test/unit-tests/ExpectationPolicies.cpp diff --git a/test/Matcher.cpp b/test/unit-tests/Matcher.cpp similarity index 100% rename from test/Matcher.cpp rename to test/unit-tests/Matcher.cpp diff --git a/test/Mock.cpp b/test/unit-tests/Mock.cpp similarity index 100% rename from test/Mock.cpp rename to test/unit-tests/Mock.cpp diff --git a/test/Printer.cpp b/test/unit-tests/Printer.cpp similarity index 100% rename from test/Printer.cpp rename to test/unit-tests/Printer.cpp diff --git a/test/Reporter.cpp b/test/unit-tests/Reporter.cpp similarity index 100% rename from test/Reporter.cpp rename to test/unit-tests/Reporter.cpp diff --git a/test/Sequence.cpp b/test/unit-tests/Sequence.cpp similarity index 100% rename from test/Sequence.cpp rename to test/unit-tests/Sequence.cpp diff --git a/test/TestReporter.hpp b/test/unit-tests/TestReporter.hpp similarity index 100% rename from test/TestReporter.hpp rename to test/unit-tests/TestReporter.hpp diff --git a/test/TestTypes.hpp b/test/unit-tests/TestTypes.hpp similarity index 100% rename from test/TestTypes.hpp rename to test/unit-tests/TestTypes.hpp diff --git a/test/TypeTraits.cpp b/test/unit-tests/TypeTraits.cpp similarity index 100% rename from test/TypeTraits.cpp rename to test/unit-tests/TypeTraits.cpp diff --git a/test/Utility.cpp b/test/unit-tests/Utility.cpp similarity index 100% rename from test/Utility.cpp rename to test/unit-tests/Utility.cpp From c6bb2cb88db4c3f91b200aa511998876e6eae7bc Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 20:47:13 +0200 Subject: [PATCH 44/68] feat: matchers return their description as std::optional --- include/mimic++/ExpectationPolicies.hpp | 11 ++++++++--- include/mimic++/Matcher.hpp | 3 ++- test/unit-tests/ExpectationPolicies.cpp | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index 40eeca82c..afc154242 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -358,11 +358,16 @@ namespace mimicpp::expectation_policies } [[nodiscard]] - StringT describe() const + std::optional describe() const { - return std::invoke( + if (const std::optional description = m_Matcher.describe()) + { + return std::invoke( m_Describer, - m_Matcher.describe()); + *description); + } + + return std::nullopt; } private: diff --git a/include/mimic++/Matcher.hpp b/include/mimic++/Matcher.hpp index a1ad416cf..47b64e0e3 100644 --- a/include/mimic++/Matcher.hpp +++ b/include/mimic++/Matcher.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +28,7 @@ namespace mimicpp && requires(const T& matcher, Target& target) { { matcher.matches(target) } -> std::convertible_to; - { matcher.describe() } -> std::convertible_to; + { matcher.describe() } -> std::convertible_to>; }; /** diff --git a/test/unit-tests/ExpectationPolicies.cpp b/test/unit-tests/ExpectationPolicies.cpp index da6edcde1..a2848511a 100644 --- a/test/unit-tests/ExpectationPolicies.cpp +++ b/test/unit-tests/ExpectationPolicies.cpp @@ -469,8 +469,10 @@ TEST_CASE( REQUIRE_CALL(describer, Invoke("matcher description")) .RETURN("expect that: matcher description"); + const auto description = policy.describe(); + REQUIRE(description); REQUIRE_THAT( - policy.describe(), + *description, Catch::Matchers::Equals("expect that: matcher description")); } @@ -1144,8 +1146,9 @@ TEST_CASE( REQUIRE_CALL(matcher, describe()) .RETURN("matcher description"); + const auto description = policy.describe(); REQUIRE_THAT( - policy.describe(), + *description, Catch::Matchers::Equals("expect: arg[0] matcher description")); } From 2cddd8ef10eb09e40cce29185bd32c998e65ae5d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 20:48:10 +0200 Subject: [PATCH 45/68] revisit: slightly adjust stringify_match_report --- include/mimic++/Reporter.hpp | 8 +++++--- test/unit-tests/Reporter.cpp | 28 +++++++++++++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 04d6726d5..dbf7c4af1 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -332,19 +332,19 @@ namespace mimicpp switch (evaluate_match_report(report)) { case MatchResult::full: - out << "Matched expectation:\n"; + out << "Matched expectation: {\n"; break; case MatchResult::inapplicable: format_to( std::ostreambuf_iterator{out}, - "Inapplicable, but otherwise matched expectation:\n" + "Inapplicable, but otherwise matched expectation: {{\n" "reason: {}\n", report.timesReport.description.value_or("No reason provided.")); break; case MatchResult::none: - out << "Unmatched expectation:\n"; + out << "Unmatched expectation: {\n"; break; // GCOVR_EXCL_START @@ -377,6 +377,8 @@ namespace mimicpp } } + out << "}\n"; + return std::move(out).str(); } diff --git a/test/unit-tests/Reporter.cpp b/test/unit-tests/Reporter.cpp index 386b3fef6..32a142ad2 100644 --- a/test/unit-tests/Reporter.cpp +++ b/test/unit-tests/Reporter.cpp @@ -726,7 +726,8 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Matched expectation:\n")); + "Matched expectation: {\n" + "}\n")); } SECTION("When contains requirements.") @@ -743,10 +744,11 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Matched expectation:\n" + "Matched expectation: {\n" "passed:\n" "\tRequirement1 description,\n" - "\tRequirement2 description,\n")); + "\tRequirement2 description,\n" + "}\n")); } } @@ -763,8 +765,9 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Inapplicable, but otherwise matched expectation:\n" - "reason: finalize description\n")); + "Inapplicable, but otherwise matched expectation: {\n" + "reason: finalize description\n" + "}\n")); } SECTION("When contains requirements.") @@ -781,11 +784,12 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Inapplicable, but otherwise matched expectation:\n" + "Inapplicable, but otherwise matched expectation: {\n" "reason: finalize description\n" "passed:\n" "\tRequirement1 description,\n" - "\tRequirement2 description,\n")); + "\tRequirement2 description,\n" + "}\n")); } } @@ -805,10 +809,11 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Unmatched expectation:\n" + "Unmatched expectation: {\n" "failed:\n" "\tRequirement1 description,\n" - "\tRequirement2 description,\n")); + "\tRequirement2 description,\n" + "}\n")); } SECTION("When contains only mixed requirements.") @@ -825,11 +830,12 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), Matches::Equals( - "Unmatched expectation:\n" + "Unmatched expectation: {\n" "failed:\n" "\tRequirement2 description,\n" "passed:\n" - "\tRequirement1 description,\n")); + "\tRequirement1 description,\n" + "}\n")); } } } From 143b548c7870dfdef244ad427fd17d971b189fd3 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 20:48:26 +0200 Subject: [PATCH 46/68] feat: add detail::ReporterInstaller --- include/mimic++/Reporter.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index dbf7c4af1..845946798 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -723,6 +723,21 @@ namespace mimicpp detail::get_reporter() = std::make_unique( std::forward(args)...); } + + namespace detail + { + template + class ReporterInstaller + { + public: + template + explicit ReporterInstaller(Args&&... args) + { + install_reporter( + std::forward(args)...); + } + }; + } } #endif From d0c1eab3f7b348754dfffe3a9a2efa09a274ea48 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 20:48:59 +0200 Subject: [PATCH 47/68] revisit: WildcardMatcher::describe return std::nullopt --- include/mimic++/Matcher.hpp | 4 ++-- test/unit-tests/Matcher.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/mimic++/Matcher.hpp b/include/mimic++/Matcher.hpp index 47b64e0e3..dbb884924 100644 --- a/include/mimic++/Matcher.hpp +++ b/include/mimic++/Matcher.hpp @@ -154,9 +154,9 @@ namespace mimicpp return true; } - static constexpr StringT describe() + static constexpr std::nullopt_t describe() noexcept { - return "has no constraints"; + return std::nullopt; } }; } diff --git a/test/unit-tests/Matcher.cpp b/test/unit-tests/Matcher.cpp index a5c16847e..a0e605850 100644 --- a/test/unit-tests/Matcher.cpp +++ b/test/unit-tests/Matcher.cpp @@ -171,9 +171,7 @@ TEST_CASE( constexpr int value{42}; REQUIRE(matches::_.matches(value)); - REQUIRE_THAT( - matches::_.describe(), - Catch::Matchers::Equals("has no constraints")); + REQUIRE(std::optional{} == matches::_.describe()); } TEST_CASE( From a1e184c7882498002a67916d5571fb84ade59992 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 21:37:03 +0200 Subject: [PATCH 48/68] fix: make ScopedExpectation DTor and ExpectationCollection::remove not noexcept --- include/mimic++/Expectation.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 04625df0c..3bb68f495 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -110,7 +110,7 @@ namespace mimicpp m_Expectations.emplace_back(std::move(expectation)); } - void remove(std::shared_ptr expectation) noexcept + void remove(std::shared_ptr expectation) { const std::scoped_lock lock{m_ExpectationsMx}; @@ -326,7 +326,7 @@ namespace mimicpp using StorageT = ExpectationCollection; using ExpectationT = Expectation; - ~ScopedExpectation() + ~ScopedExpectation() noexcept(false) { if (m_Storage && m_Expectation) From d8bad106b8d193960fca35cc082290e8ceea1005 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 21:37:24 +0200 Subject: [PATCH 49/68] fix: correct times stringification --- include/mimic++/ExpectationPolicies.hpp | 33 +++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index afc154242..8e29e39aa 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -21,27 +21,26 @@ namespace mimicpp::expectation_policies::detail [[nodiscard]] inline StringT describe_times_state(const std::size_t current, const std::size_t min, const std::size_t max) { - const StringT currentDescription = std::invoke( - [&]() -> StringT + const auto verbalize = [](const std::size_t value)-> StringT + { + switch (value) { - switch (current) - { - case 0: - return "never"; - case 1: - return "once"; - case 2: - return "twice"; - default: - return format::format("{} times", current); - } - }); + case 0: + return "never"; + case 1: + return "once"; + case 2: + return "twice"; + default: + return format::format("{} times", value); + } + }; if (current == max) { return format::format( "inapplicable: already saturated (matched {})", - currentDescription); + verbalize(current)); } if (min <= current) @@ -65,14 +64,13 @@ namespace mimicpp::expectation_policies::detail return format::format( "exactly {}", - currentDescription); + verbalize(max)); }); if (current == 0) { return format::format( "unsatisfied: matched never - {} is expected", - current, intervalDescription); } @@ -80,7 +78,6 @@ namespace mimicpp::expectation_policies::detail { return format::format( "unsatisfied: matched just once - {} is expected", - current, intervalDescription); } From e0cf37443481881cc675ba702b01a4d0f1712844 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:00:43 +0200 Subject: [PATCH 50/68] feat: Catch2 adapter --- .github/workflows/build.yml | 7 + .github/workflows/coverage.yml | 8 +- include/mimic++/adapters/Catch2.hpp | 181 +++++++++++++++++++++ test/CMakeLists.txt | 7 +- test/adapter-tests/CMakeLists.txt | 1 + test/adapter-tests/catch2/CMakeLists.txt | 22 +++ test/adapter-tests/catch2/main.cpp | 191 +++++++++++++++++++++++ 7 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 include/mimic++/adapters/Catch2.hpp create mode 100644 test/adapter-tests/CMakeLists.txt create mode 100644 test/adapter-tests/catch2/CMakeLists.txt create mode 100644 test/adapter-tests/catch2/main.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 661d991f5..4798ff8aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -173,6 +173,7 @@ jobs: -D CMAKE_CXX_FLAGS:STRING="${{ env.CXX_FLAGS }}" \ -D CMAKE_EXE_LINKER_FLAGS:STRING="${{ env.LINK_FLAGS }}" \ -D MIMICPP_FORCED_CXX_STANDARD="${{ matrix.cxx_standard }}" \ + -D MIMICPP_ENABLE_ADAPTER_TESTS=YES \ ${{ env.CMAKE_CONFIG_EXTRA }} - name: Build @@ -188,6 +189,12 @@ jobs: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir build/test/unit-tests -C ${{ matrix.build_mode }} -j4 + - name: Run adapter tests + shell: bash + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir build/test/adapter-tests -C ${{ matrix.build_mode }} -j4 + - name: Run examples shell: bash env: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 45bf5ada7..d99ca5934 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,8 @@ jobs: cmake -S . \ -B build \ -D CMAKE_CXX_FLAGS="-g -O0 --coverage -fno-inline -fprofile-abs-path -fkeep-inline-functions -fkeep-static-functions" \ - -D CMAKE_BUILD_TYPE="Debug" + -D CMAKE_BUILD_TYPE="Debug" \ + -D MIMICPP_ENABLE_ADAPTER_TESTS=YES - name: Build run: | @@ -44,6 +45,11 @@ jobs: CTEST_OUTPUT_ON_FAILURE: 1 run: ctest --test-dir build/test/unit-tests -C Debug -j4 + - name: Run adapter tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --test-dir build/test/adapter-tests -C Debug -j4 + - name: Run examples env: CTEST_OUTPUT_ON_FAILURE: 1 diff --git a/include/mimic++/adapters/Catch2.hpp b/include/mimic++/adapters/Catch2.hpp new file mode 100644 index 000000000..a76197125 --- /dev/null +++ b/include/mimic++/adapters/Catch2.hpp @@ -0,0 +1,181 @@ +#ifndef MIMICPP_ADAPTERS_CATCH2_HPP +#define MIMICPP_ADAPTERS_CATCH2_HPP + +#pragma once + +#include "mimic++/Reporter.hpp" + +#include + +#if __has_include() + #include +#else + #error "Unable to find catch2 includes." +#endif + +namespace mimicpp::detail +{ + [[noreturn]] + inline void send_catch_fail(const StringViewT msg) + { +#ifdef CATCH_CONFIG_PREFIX_ALL + CATCH_FAIL(msg); +#else + FAIL(msg); +#endif + + unreachable(); + } + + inline void send_catch_succeed(const StringViewT msg) + { +#ifdef CATCH_CONFIG_PREFIX_ALL + CATCH_SUCCEED(msg); +#else + SUCCEED(msg); +#endif + } + + inline void send_catch_warn(const StringViewT msg) + { +#ifdef CATCH_CONFIG_PREFIX_ALL + CATCH_WARN(msg); +#else + WARN(msg); +#endif + } +} + +namespace mimicpp +{ + class Catch2Reporter final + : public IReporter + { + public: + [[noreturn]] + void report_no_matches(const CallReport call, const std::vector matchReports) override + { + StringStreamT ss{}; + format_to( + std::ostreambuf_iterator{ss}, + "No match for {}\n", + stringify_call_report(call)); + + if (std::ranges::empty(matchReports)) + { + ss << "No expectations available.\n"; + } + else + { + format_to( + std::ostreambuf_iterator{ss}, + "{} available expectations:\n", + std::ranges::size(matchReports)); + + for (const auto& report : matchReports) + { + ss << stringify_match_report(report) << "\n"; + } + } + + detail::send_catch_fail(ss.view()); + } + + [[noreturn]] + void report_inapplicable_matches(const CallReport call, const std::vector matchReports) override + { + StringStreamT ss{}; + format_to( + std::ostreambuf_iterator{ss}, + "No applicable match for {}\n", + stringify_call_report(call)); + + ss << "Tested expectations:\n"; + for (const auto& report : matchReports) + { + ss << stringify_match_report(report) << "\n"; + } + + detail::send_catch_fail(ss.view()); + } + + void report_full_match(const CallReport call, const MatchReport matchReport) noexcept override + { + StringStreamT ss{}; + format_to( + std::ostreambuf_iterator{ss}, + "Found match for {}\n", + stringify_call_report(call)); + + ss << stringify_match_report(matchReport) << "\n"; + + detail::send_catch_succeed(ss.view()); + } + + void report_unfulfilled_expectation(const ExpectationReport expectationReport) override + { + if (0 == std::uncaught_exceptions()) + { + StringStreamT ss{}; + ss << "Unfulfilled expectation:\n" + << stringify_expectation_report(expectationReport) << "\n"; + + detail::send_catch_fail(ss.view()); + } + } + + void report_error(const StringT message) override + { + if (0 == std::uncaught_exceptions()) + { + detail::send_catch_fail(message); + } + } + + void report_unhandled_exception( + const CallReport call, + const ExpectationReport expectationReport, + const std::exception_ptr exception + ) override + { + StringStreamT ss{}; + ss << "Unhandled exception: "; + + try + { + std::rethrow_exception(exception); + } + catch (const std::exception& e) + { + format_to( + std::ostreambuf_iterator{ss}, + "what: {}\n", + e.what()); + } + catch (...) + { + ss << "Unknown exception type.\n"; + } + + format_to( + std::ostreambuf_iterator{ss}, + "while checking expectation:\n" + "{}\n", + stringify_expectation_report(expectationReport)); + + format_to( + std::ostreambuf_iterator{ss}, + "For {}\n", + stringify_call_report(call)); + + detail::send_catch_warn(ss.view()); + } + }; +} + +namespace mimicpp::detail +{ + inline const ReporterInstaller installer{}; +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7fa162dff..02012d6c0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1 +1,6 @@ -add_subdirectory("unit-tests") \ No newline at end of file +add_subdirectory("unit-tests") + +option(MIMICPP_ENABLE_ADAPTER_TESTS "Determines, whether the adapter tests shall be built." OFF) +if (MIMICPP_ENABLE_ADAPTER_TESTS) + add_subdirectory("adapter-tests") +endif() diff --git a/test/adapter-tests/CMakeLists.txt b/test/adapter-tests/CMakeLists.txt new file mode 100644 index 000000000..a368fa1b0 --- /dev/null +++ b/test/adapter-tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory("catch2") \ No newline at end of file diff --git a/test/adapter-tests/catch2/CMakeLists.txt b/test/adapter-tests/catch2/CMakeLists.txt new file mode 100644 index 000000000..7463f97ab --- /dev/null +++ b/test/adapter-tests/catch2/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(Catch2 REQUIRED) +include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") + +add_executable( + mimicpp-adapter-tests-catch2 + "main.cpp" +) + +target_link_libraries( + mimicpp-adapter-tests-catch2 + PRIVATE + mimicpp::mimicpp + Catch2::Catch2WithMain +) + +target_compile_features( + mimicpp + INTERFACE + cxx_std_${CMAKE_CXX_STANDARD} +) + +catch_discover_tests(mimicpp-adapter-tests-catch2) diff --git a/test/adapter-tests/catch2/main.cpp b/test/adapter-tests/catch2/main.cpp new file mode 100644 index 000000000..f5a7b61ea --- /dev/null +++ b/test/adapter-tests/catch2/main.cpp @@ -0,0 +1,191 @@ +// // Copyright Dominic (DNKpp) Koepke 2024 - 2024. +// // Distributed under the Boost Software License, Version 1.0. +// // (See accompanying file LICENSE_1_0.txt or copy at +// // https://www.boost.org/LICENSE_1_0.txt) + +#include "catch2/catch_test_macros.hpp" +#include "mimic++/Mock.hpp" +#include "mimic++/adapters/Catch2.hpp" + +#include +#include + +namespace +{ + inline std::atomic_int g_SuccessCounter{0}; + + class SuccessListener final + : public Catch::EventListenerBase + { + using SuperT = EventListenerBase; + + public: + [[nodiscard]] + explicit SuccessListener(const Catch::IConfig* config) + : SuperT{config} + { + m_preferences.shouldReportAllAssertions = true; + } + + void assertionEnded(const Catch::AssertionStats& assertionStats) override + { + if (assertionStats.assertionResult.succeeded()) + { + ++g_SuccessCounter; + } + } + }; +} + +CATCH_REGISTER_LISTENER(SuccessListener) + +TEST_CASE( + "Catch2Reporter reports matches as succeeded statements.", + "[adapter][adapter::catch2]" +) +{ + g_SuccessCounter = 0; + mimicpp::Mock mock{}; + + SCOPED_EXP mock.expect_call(42); + + CHECK(g_SuccessCounter == 0); + mock(42); + REQUIRE(g_SuccessCounter == 2); // the CHECK and SUCCEED +} + +TEST_CASE( + "Catch2Reporter reports failure, when no match can be found.", + "[!shouldfail][adapter][adapter::catch2]" +) +{ + mimicpp::Mock mock{}; + + SCOPED_EXP mock.expect_call(42); + SCOPED_EXP mock.expect_call(-42); + + mock(1337); +} + +TEST_CASE( + "Catch2Reporter reports failure, when no applicable match can be found.", + "[!shouldfail][adapter][adapter::catch2]" +) +{ + mimicpp::Mock mock{}; + + SCOPED_EXP mock.expect_call(42); + + mock(42); + mock(42); +} + +namespace +{ + class TestException + { + }; + + class ThrowOnMatches + { + public: + [[maybe_unused]] + static constexpr bool is_satisfied() noexcept + { + return true; + } + + [[maybe_unused]] + static constexpr bool matches([[maybe_unused]] const auto& info) + { + throw TestException{}; + } + + [[maybe_unused]] + static constexpr std::nullopt_t describe() noexcept + { + return std::nullopt; + } + + [[maybe_unused]] + static constexpr void consume([[maybe_unused]] const auto& info) noexcept + { + } + }; +} + +TEST_CASE( + "Catch2Reporter::report_unhandled_exception just creates a warning.", + "[adapter][adapter::catch2]" +) +{ + mimicpp::Mock mock{}; + + SCOPED_EXP mock.expect_call(); + + SCOPED_EXP mock.expect_call() + | mimicpp::expect::times<0, 1>() + | ThrowOnMatches{}; + + REQUIRE_NOTHROW(mock()); +} + +TEST_CASE( + "Catch2Reporter::report_unfulfilled_expectation cancels the test, when no other exception exists.", + "[!shouldfail][adapter][adapter::catch2]" +) +{ + mimicpp::Mock mock{}; + + SCOPED_EXP mock.expect_call(); +} + +TEST_CASE( + "Catch2Reporter::report_unfulfilled_expectation does nothing, when already an uncaught exception exists.", + "[adapter][adapter::catch2]" +) +{ + mimicpp::Mock mock{}; + + const auto runTest = [&] + { + SCOPED_EXP mock.expect_call(); + throw 42; + }; + + REQUIRE_THROWS_AS( + runTest(), + int); +} + +TEST_CASE( + "Catch2Reporter::report_error cancels the test, when no other exception exists.", + "[!shouldfail][adapter][adapter::catch2]" +) +{ + mimicpp::detail::report_error("Hello, World!"); +} + +TEST_CASE( + "Catch2Reporter::report_error does nothing, when already an uncaught exception exists.", + "[adapter][adapter::catch2]" +) +{ + struct helper + { + ~helper() + { + mimicpp::detail::report_error("Hello, World!"); + } + }; + + const auto runTest = [] + { + helper h{}; + throw 42; + }; + + REQUIRE_THROWS_AS( + runTest(), + int); +} From a362d4975d979b22b1ab83496fc779b656887896 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:08:01 +0200 Subject: [PATCH 51/68] fix: add missing atomic include --- test/adapter-tests/catch2/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/adapter-tests/catch2/main.cpp b/test/adapter-tests/catch2/main.cpp index f5a7b61ea..4cc592453 100644 --- a/test/adapter-tests/catch2/main.cpp +++ b/test/adapter-tests/catch2/main.cpp @@ -10,6 +10,8 @@ #include #include +#include + namespace { inline std::atomic_int g_SuccessCounter{0}; From 5c075b53672b43beaed0f61c9316cc5a5898c988 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:08:08 +0200 Subject: [PATCH 52/68] fix: please clang --- test/adapter-tests/catch2/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/adapter-tests/catch2/main.cpp b/test/adapter-tests/catch2/main.cpp index 4cc592453..7f0ed7160 100644 --- a/test/adapter-tests/catch2/main.cpp +++ b/test/adapter-tests/catch2/main.cpp @@ -98,7 +98,7 @@ namespace } [[maybe_unused]] - static constexpr bool matches([[maybe_unused]] const auto& info) + static bool matches([[maybe_unused]] const auto& info) { throw TestException{}; } From dee99b3eeed6ca48d5211b7988ed1da2299e1865 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:18:00 +0200 Subject: [PATCH 53/68] refactor: extract executable target setup into SetupTestTarget.cmake --- cmake/SetupTestTarget.cmake | 35 ++++++++++++++++++++++++++++++++++ examples/CMakeLists.txt | 27 ++------------------------ test/unit-tests/CMakeLists.txt | 27 ++------------------------ 3 files changed, 39 insertions(+), 50 deletions(-) create mode 100644 cmake/SetupTestTarget.cmake diff --git a/cmake/SetupTestTarget.cmake b/cmake/SetupTestTarget.cmake new file mode 100644 index 000000000..a87fa2eb0 --- /dev/null +++ b/cmake/SetupTestTarget.cmake @@ -0,0 +1,35 @@ +# Copyright Dominic (DNKpp) Koepke 2024 - 2024. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# https://www.boost.org/LICENSE_1_0.txt) + + +function(setup_test_target TARGET_NAME) + + if (MSVC) + + # When using github ci, exceptions seems to be disabled by default. + target_compile_options( + ${TARGET_NAME} + PRIVATE + /EHsc + ) + + endif() + + if (SANITIZE_ADDRESS) + + # workaround linker errors on msvc + # see: https://learn.microsoft.com/en-us/answers/questions/864574/enabling-address-sanitizer-results-in-error-lnk203 + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + $<$:_DISABLE_VECTOR_ANNOTATION> + $<$:_DISABLE_STRING_ANNOTATION> + ) + + endif() + + add_sanitizers(${TARGET_NAME}) + +endfunction() \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 93880ad22..394ff4c39 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,31 +9,8 @@ add_executable( "SideEffects.cpp" ) -if (MSVC) - - # When using github ci, exceptions seems to be disabled by default. - target_compile_options( - mimicpp-examples - PRIVATE - /EHsc - ) - -endif() - -if (SANITIZE_ADDRESS) - - # workaround linker errors on msvc - # see: https://learn.microsoft.com/en-us/answers/questions/864574/enabling-address-sanitizer-results-in-error-lnk203 - target_compile_definitions( - mimicpp-examples - PRIVATE - $<$:_DISABLE_VECTOR_ANNOTATION> - $<$:_DISABLE_STRING_ANNOTATION> - ) - -endif() - -add_sanitizers(mimicpp-tests) +include(SetupTestTarget) +setup_test_target(mimicpp-examples) target_link_libraries( mimicpp-examples diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index f56325445..96de2eb28 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -18,31 +18,8 @@ add_executable( "Utility.cpp" ) -if (MSVC) - - # When using github ci, exceptions seems to be disabled by default. - target_compile_options( - mimicpp-tests - PRIVATE - /EHsc - ) - -endif() - -if (SANITIZE_ADDRESS) - - # workaround linker errors on msvc - # see: https://learn.microsoft.com/en-us/answers/questions/864574/enabling-address-sanitizer-results-in-error-lnk203 - target_compile_definitions( - mimicpp-tests - PRIVATE - $<$:_DISABLE_VECTOR_ANNOTATION> - $<$:_DISABLE_STRING_ANNOTATION> - ) - -endif() - -add_sanitizers(mimicpp-tests) +include(SetupTestTarget) +setup_test_target(mimicpp-tests) target_link_libraries( mimicpp-tests From e133bd04fa6cfb5edb2794ffe13f00920098c633 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:18:25 +0200 Subject: [PATCH 54/68] fix: utilize setup_test_target for adapter-test/catch2 --- test/adapter-tests/catch2/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/adapter-tests/catch2/CMakeLists.txt b/test/adapter-tests/catch2/CMakeLists.txt index 7463f97ab..2d121867f 100644 --- a/test/adapter-tests/catch2/CMakeLists.txt +++ b/test/adapter-tests/catch2/CMakeLists.txt @@ -6,6 +6,9 @@ add_executable( "main.cpp" ) +include(SetupTestTarget) +setup_test_target(mimicpp-adapter-tests-catch2) + target_link_libraries( mimicpp-adapter-tests-catch2 PRIVATE From ee3564263ae86b5a5c563cd129a6d2071970ba3c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:27:40 +0200 Subject: [PATCH 55/68] deps: upgrade catch2 to version 3.6.0 --- examples/CMakeLists.txt | 2 +- test/unit-tests/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 394ff4c39..2fb452e83 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -CPMAddPackage("gh:catchorg/Catch2@3.5.4") +find_package(Catch2 REQUIRED) include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") add_executable( diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index 96de2eb28..fe35abcc0 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -1,4 +1,4 @@ -CPMAddPackage("gh:catchorg/Catch2@3.5.4") +CPMAddPackage("gh:catchorg/Catch2@3.6.0") CPMAddPackage("gh:rollbear/trompeloeil@47") include("${Catch2_SOURCE_DIR}/extras/Catch.cmake") From 4f7d8095ffa746ad6d0737780bfd410f14ef4c7c Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:27:57 +0200 Subject: [PATCH 56/68] fix: correctly use CATCH_CONFIG_PREFIX_MESSAGES --- include/mimic++/adapters/Catch2.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mimic++/adapters/Catch2.hpp b/include/mimic++/adapters/Catch2.hpp index a76197125..9d200525a 100644 --- a/include/mimic++/adapters/Catch2.hpp +++ b/include/mimic++/adapters/Catch2.hpp @@ -38,7 +38,7 @@ namespace mimicpp::detail inline void send_catch_warn(const StringViewT msg) { -#ifdef CATCH_CONFIG_PREFIX_ALL +#ifdef CATCH_CONFIG_PREFIX_MESSAGES CATCH_WARN(msg); #else WARN(msg); From ed27395c6e7e4bf65572cded087ecd50aa26ce1e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Mon, 6 May 2024 22:30:21 +0200 Subject: [PATCH 57/68] fix: correctly apply MIMICPP_FORCED_CXX_STANDARD --- CMakeLists.txt | 10 +++++----- examples/CMakeLists.txt | 6 ------ test/adapter-tests/catch2/CMakeLists.txt | 6 ------ test/unit-tests/CMakeLists.txt | 6 ------ 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 358547844..9d15aaf2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,14 @@ target_include_directories( ) set(CMAKE_CXX_STANDARD 20) +if (MIMICPP_FORCED_CXX_STANDARD) + set(CMAKE_CXX_STANDARD ${MIMICPP_FORCED_CXX_STANDARD}) +endif() + target_compile_features( mimicpp INTERFACE - cxx_std_20 + cxx_std_${CMAKE_CXX_STANDARD} ) if (CMAKE_SOURCE_DIR STREQUAL mimicpp_SOURCE_DIR) @@ -41,10 +45,6 @@ else() set(IS_TOP_LEVEL_PROJECT OFF) endif() -if (MIMICPP_FORCED_CXX_STANDARD) - set(CMAKE_CXX_STANDARD ${MIMICPP_FORCED_CXX_STANDARD}) -endif() - OPTION(MIMICPP_BUILD_TESTS "Determines, whether the tests shall be built." ${IS_TOP_LEVEL_PROJECT}) if (MIMICPP_BUILD_TESTS) include(CTest) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2fb452e83..f9110605f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,10 +19,4 @@ target_link_libraries( Catch2::Catch2WithMain ) -target_compile_features( - mimicpp - INTERFACE - cxx_std_${CMAKE_CXX_STANDARD} -) - catch_discover_tests(mimicpp-examples) diff --git a/test/adapter-tests/catch2/CMakeLists.txt b/test/adapter-tests/catch2/CMakeLists.txt index 2d121867f..911e5db97 100644 --- a/test/adapter-tests/catch2/CMakeLists.txt +++ b/test/adapter-tests/catch2/CMakeLists.txt @@ -16,10 +16,4 @@ target_link_libraries( Catch2::Catch2WithMain ) -target_compile_features( - mimicpp - INTERFACE - cxx_std_${CMAKE_CXX_STANDARD} -) - catch_discover_tests(mimicpp-adapter-tests-catch2) diff --git a/test/unit-tests/CMakeLists.txt b/test/unit-tests/CMakeLists.txt index fe35abcc0..9ea9bb810 100644 --- a/test/unit-tests/CMakeLists.txt +++ b/test/unit-tests/CMakeLists.txt @@ -29,10 +29,4 @@ target_link_libraries( trompeloeil::trompeloeil ) -target_compile_features( - mimicpp - INTERFACE - cxx_std_${CMAKE_CXX_STANDARD} -) - catch_discover_tests(mimicpp-tests) From c3dabff9db818548fc67dbe8880be531897d0f24 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 10:28:16 +0200 Subject: [PATCH 58/68] ci: correct flag setup --- .github/workflows/build.yml | 6 ++---- .github/workflows/coverage.yml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4798ff8aa..504f8429e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,8 +133,8 @@ jobs: if: ${{ matrix.config.compiler_name == 'clang' && matrix.config.libcxx == true }} shell: bash run: | - echo "CXX_FLAGS=$(echo $CXX_FLAGS -stdlib=libc++)" >> $GITHUB_ENV - echo "LINK_FLAGS=$(echo $LINK_FLAGS -lc++abi)" >> $GITHUB_ENV + echo "CXXFLAGS=$(echo $CXXFLAGS -stdlib=libc++)" >> $GITHUB_ENV + echo "LDFLAGS=$(echo LDFLAGS -lc++abi)" >> $GITHUB_ENV - name: Setup linux if: ${{ matrix.config.prefix == 'Linux' }} @@ -170,8 +170,6 @@ jobs: -S . \ -B build \ -D CMAKE_VERBOSE_MAKEFILE=yes \ - -D CMAKE_CXX_FLAGS:STRING="${{ env.CXX_FLAGS }}" \ - -D CMAKE_EXE_LINKER_FLAGS:STRING="${{ env.LINK_FLAGS }}" \ -D MIMICPP_FORCED_CXX_STANDARD="${{ matrix.cxx_standard }}" \ -D MIMICPP_ENABLE_ADAPTER_TESTS=YES \ ${{ env.CMAKE_CONFIG_EXTRA }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d99ca5934..5dbf4103f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -29,10 +29,10 @@ jobs: - name: Configure env: LDFLAGS: "-fprofile-arcs" + CXXFLAGS: "-g -O0 --coverage -fno-inline -fprofile-abs-path -fkeep-inline-functions -fkeep-static-functions" run: | cmake -S . \ -B build \ - -D CMAKE_CXX_FLAGS="-g -O0 --coverage -fno-inline -fprofile-abs-path -fkeep-inline-functions -fkeep-static-functions" \ -D CMAKE_BUILD_TYPE="Debug" \ -D MIMICPP_ENABLE_ADAPTER_TESTS=YES From 283f7e4550b091ff52f3466ba523b619934a9e21 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 10:30:59 +0200 Subject: [PATCH 59/68] ci: fix previous attempt --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 504f8429e..22a7fd541 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -134,7 +134,7 @@ jobs: shell: bash run: | echo "CXXFLAGS=$(echo $CXXFLAGS -stdlib=libc++)" >> $GITHUB_ENV - echo "LDFLAGS=$(echo LDFLAGS -lc++abi)" >> $GITHUB_ENV + echo "LDFLAGS=$(echo $LDFLAGS -lc++abi)" >> $GITHUB_ENV - name: Setup linux if: ${{ matrix.config.prefix == 'Linux' }} From 0057f029ba9c9207110cd986eab41f0288bd7c93 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 10:40:21 +0200 Subject: [PATCH 60/68] ci: exclude unreachable() function from coverage --- .github/workflows/coverage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5dbf4103f..1fdac0f48 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -61,7 +61,9 @@ jobs: git config --global --add safe.directory /__w/mimicpp/mimicpp gcovr --root build/test/unit-tests --filter "include/mimic++" --keep -j4 -v \ --exclude-lines-by-pattern "\s*assert\(" \ + --exclude-lines-by-pattern "\s*unreachable\(\);" \ --exclude-unreachable-branches \ + --exclude-function-lines \ --exclude-noncode-lines \ --exclude-throw-branches \ --decisions \ From 45e572fe03fd9f083112ee52164c792a008e157d Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 10:47:19 +0200 Subject: [PATCH 61/68] env: remove explicit msvc flags --- cmake/SetupTestTarget.cmake | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cmake/SetupTestTarget.cmake b/cmake/SetupTestTarget.cmake index a87fa2eb0..cd4e7a102 100644 --- a/cmake/SetupTestTarget.cmake +++ b/cmake/SetupTestTarget.cmake @@ -6,17 +6,6 @@ function(setup_test_target TARGET_NAME) - if (MSVC) - - # When using github ci, exceptions seems to be disabled by default. - target_compile_options( - ${TARGET_NAME} - PRIVATE - /EHsc - ) - - endif() - if (SANITIZE_ADDRESS) # workaround linker errors on msvc From d14073bffb15734b6e9211fee034781c80605a4f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 10:52:00 +0200 Subject: [PATCH 62/68] refactor: simplify detail::describe_times_state --- include/mimic++/ExpectationPolicies.hpp | 45 +++++++++---------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/include/mimic++/ExpectationPolicies.hpp b/include/mimic++/ExpectationPolicies.hpp index 8e29e39aa..ce466132d 100644 --- a/include/mimic++/ExpectationPolicies.hpp +++ b/include/mimic++/ExpectationPolicies.hpp @@ -21,7 +21,7 @@ namespace mimicpp::expectation_policies::detail [[nodiscard]] inline StringT describe_times_state(const std::size_t current, const std::size_t min, const std::size_t max) { - const auto verbalize = [](const std::size_t value)-> StringT + const auto verbalizeValue = [](const std::size_t value)-> StringT { switch (value) { @@ -40,7 +40,7 @@ namespace mimicpp::expectation_policies::detail { return format::format( "inapplicable: already saturated (matched {})", - verbalize(current)); + verbalizeValue(current)); } if (min <= current) @@ -51,40 +51,25 @@ namespace mimicpp::expectation_policies::detail max); } - const StringT intervalDescription = std::invoke( - [&]() -> StringT + const auto verbalizeInterval = [verbalizeValue](const std::size_t start, const std::size_t end) + { + if (start < end) { - if (min < max) - { - return format::format( - "between {} and {} times", - min, - max); - } - return format::format( - "exactly {}", - verbalize(max)); - }); - - if (current == 0) - { - return format::format( - "unsatisfied: matched never - {} is expected", - intervalDescription); - } + "between {} and {} times", + start, + end); + } - if (current == 1) - { return format::format( - "unsatisfied: matched just once - {} is expected", - intervalDescription); - } + "exactly {}", + verbalizeValue(end)); + }; return format::format( - "unsatisfied: matched just {} times - {} is expected", - current, - intervalDescription); + "unsatisfied: matched {} - {} is expected", + verbalizeValue(current), + verbalizeInterval(min, max)); } } From c5d9de0ed7d6ec81bec3495c11b5c10a5caad795 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 11:37:49 +0200 Subject: [PATCH 63/68] docs: add catcch2 adapter docs --- docs/Doxyfile.in | 4 ++-- include/mimic++/Reporter.hpp | 16 +++++++++++++++- include/mimic++/adapters/Catch2.hpp | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index f25f9f983..6471afa2a 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -502,7 +502,7 @@ LOOKUP_CACHE_SIZE = 0 # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. -NUM_PROC_THREADS = 1 +NUM_PROC_THREADS = 0 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to @@ -510,7 +510,7 @@ NUM_PROC_THREADS = 1 # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. -TIMESTAMP = NO +TIMESTAMP = DATE #--------------------------------------------------------------------------- # Build related configuration options diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 845946798..22ec0edd5 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -84,7 +84,6 @@ namespace mimicpp * \tparam Params The function parameter types. * \param callInfo The call info. * \return The call report. - * \relatesalso CallReport * \relatesalso call::Info */ template @@ -738,6 +737,21 @@ namespace mimicpp } }; } + + /** + * \defgroup REPORTING_ADAPTERS test framework adapters + * \ingroup REPORTING + * \brief Reporter integrations for various third-party frameworks. + * \details These reporters are specialized implementations, which provide seamless integrations of ``mimic++`` into the desired + * unit-test framework. Integrations are enabled by simply including the specific header into any source file. The include order + * doesn't matter. + * + * \note Including multiple headers of the ``adapters`` subdirectory into one executable is possible, but with caveats. It's unspecified + * which reporter will be active at the program start. So, if you need multiple reporters in one executable, you should explicitly + * install the desired reporter on a per test case basis. + * + *\{ + */ } #endif diff --git a/include/mimic++/adapters/Catch2.hpp b/include/mimic++/adapters/Catch2.hpp index 9d200525a..b6b287614 100644 --- a/include/mimic++/adapters/Catch2.hpp +++ b/include/mimic++/adapters/Catch2.hpp @@ -48,6 +48,14 @@ namespace mimicpp::detail namespace mimicpp { + /** + * \brief Reporter for the integration into Catch2. + * \ingroup REPORTING_ADAPTERS + * \details This reporter enables the integration of ``mimic++`` into ``Catch2`` and prefixes the headers + * of ``Catch2`` with ``catch2/``. + * + * This reporter installs itself by simply including this header file into any source file of the test executable. + */ class Catch2Reporter final : public IReporter { From a45ed58a4724527b70984f5febaf6330b9c56f05 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 14:50:12 +0200 Subject: [PATCH 64/68] feat: ScopedExpectation captures std::source_location --- include/mimic++/Expectation.hpp | 31 +++++++++++++++++------ include/mimic++/ExpectationBuilder.hpp | 3 ++- test/unit-tests/Expectation.cpp | 29 ++++++++++++++++++++- test/unit-tests/ExpectationBuilder.cpp | 35 +++++++++++++++++++++++++- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 3bb68f495..1fb1c6f4a 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -231,6 +231,7 @@ namespace mimicpp && std::constructible_from && std::constructible_from constexpr explicit BasicExpectation( + const std::source_location& sourceLocation, TimesArg&& timesArg, FinalizerArg&& finalizerArg, PolicyArgs&&... args @@ -238,9 +239,10 @@ namespace mimicpp std::is_nothrow_constructible_v && std::is_nothrow_constructible_v && (std::is_nothrow_constructible_v && ...)) - : m_Times{std::forward(timesArg)}, - m_Finalizer{std::forward(finalizerArg)}, - m_Policies{std::forward(args)...} + : m_SourceLocation{sourceLocation}, + m_Policies{std::forward(args)...}, + m_Times{std::forward(timesArg)}, + m_Finalizer{std::forward(finalizerArg)} { } @@ -313,10 +315,17 @@ namespace mimicpp return m_Finalizer.finalize_call(call); } + [[nodiscard]] + constexpr const std::source_location& from() const noexcept + { + return m_SourceLocation; + } + private: + std::source_location m_SourceLocation; + PolicyListT m_Policies; [[no_unique_address]] TimesT m_Times{}; [[no_unique_address]] FinalizerT m_Finalizer{}; - PolicyListT m_Policies; }; template @@ -350,13 +359,13 @@ namespace mimicpp } template - requires requires + requires requires(const std::source_location& loc) { - { std::declval().finalize() } -> std::convertible_to; + { std::declval().finalize(loc) } -> std::convertible_to; } [[nodiscard]] - explicit(false) constexpr ScopedExpectation(T&& object) - : ScopedExpectation{std::forward(object).finalize()} + explicit(false) constexpr ScopedExpectation(T&& object, const std::source_location& loc = std::source_location::current()) + : ScopedExpectation{std::forward(object).finalize(loc)} { } @@ -377,6 +386,12 @@ namespace mimicpp throw std::runtime_error{"Expired expectation."}; } + [[nodiscard]] + const ExpectationT& expectation() const noexcept + { + return *m_Expectation; + } + private: std::shared_ptr m_Storage{}; std::shared_ptr m_Expectation{}; diff --git a/include/mimic++/ExpectationBuilder.hpp b/include/mimic++/ExpectationBuilder.hpp index e7f4c9ee2..13481fee3 100644 --- a/include/mimic++/ExpectationBuilder.hpp +++ b/include/mimic++/ExpectationBuilder.hpp @@ -124,7 +124,7 @@ namespace mimicpp } [[nodiscard]] - constexpr ScopedExpectationT finalize() && + constexpr ScopedExpectationT finalize(const std::source_location& sourceLocation) && { static_assert( finalize_policy_for, @@ -138,6 +138,7 @@ namespace mimicpp [&](auto&... policies) { return std::make_unique( + sourceLocation, std::move(m_TimesPolicy), std::move(m_FinalizePolicy), std::move(policies)...); diff --git a/test/unit-tests/Expectation.cpp b/test/unit-tests/Expectation.cpp index 609acf898..8fe074387 100644 --- a/test/unit-tests/Expectation.cpp +++ b/test/unit-tests/Expectation.cpp @@ -369,6 +369,25 @@ TEMPLATE_TEST_CASE_SIG( STATIC_REQUIRE(expected == mimicpp::times_policy); } +TEST_CASE( + "mimicpp::BasicExpectation stores std::source_location.", + "[expectation]" +) +{ + using TimesT = TimesFake; + using FinalizerT = FinalizerFake; + + constexpr auto loc = std::source_location::current(); + + mimicpp::BasicExpectation expectation{ + loc, + TimesT{}, + FinalizerT{} + }; + + REQUIRE(mimicpp::is_same_source_location(loc, expectation.from())); +} + TEST_CASE( "Times policy of mimicpp::BasicExpectation controls, how often its expectations must be matched.", "[expectation]" @@ -394,6 +413,7 @@ TEST_CASE( SECTION("With no other expectation policies.") { mimicpp::BasicExpectation expectation{ + std::source_location::current(), std::ref(times), FinalizerT{} }; @@ -439,6 +459,7 @@ TEST_CASE( { PolicyMockT policy{}; mimicpp::BasicExpectation expectation{ + std::source_location::current(), std::ref(times), FinalizerT{}, std::ref(policy) @@ -541,6 +562,7 @@ TEMPLATE_TEST_CASE( SECTION("With no policies at all.") { mimicpp::BasicExpectation expectation{ + std::source_location::current(), TimesFake{.isSatisfied = true}, FinalizerT{} }; @@ -559,6 +581,7 @@ TEMPLATE_TEST_CASE( { PolicyMockT policy{}; mimicpp::BasicExpectation expectation{ + std::source_location::current(), TimesFake{.isSatisfied = true}, FinalizerT{}, PolicyRefT{std::ref(policy)} @@ -609,6 +632,7 @@ TEMPLATE_TEST_CASE( PolicyMockT policy1{}; PolicyMockT policy2{}; mimicpp::BasicExpectation expectation{ + std::source_location::current(), TimesFake{.isSatisfied = true}, FinalizerT{}, PolicyRefT{std::ref(policy1)}, @@ -705,7 +729,7 @@ TEMPLATE_TEST_CASE( } TEST_CASE( - "mimicpp::BasicExpectation::report gathers information about its used policies.", + "mimicpp::BasicExpectation::report gathers information about the expectation.", "[expectation]" ) { @@ -729,6 +753,7 @@ TEST_CASE( TimesT, FinalizerPolicyT> expectation{ + std::source_location::current(), TimesT{std::ref(times)}, FinalizerPolicyT{} }; @@ -757,6 +782,7 @@ TEST_CASE( FinalizerPolicyT, PolicyT> expectation{ + std::source_location::current(), TimesPolicyT{}, FinalizerPolicyT{}, PolicyT{std::ref(policy)} @@ -797,6 +823,7 @@ TEMPLATE_TEST_CASE( FinalizerT finalizer{}; mimicpp::BasicExpectation expectation{ + std::source_location::current(), TimesFake{}, std::ref(finalizer) }; diff --git a/test/unit-tests/ExpectationBuilder.cpp b/test/unit-tests/ExpectationBuilder.cpp index 33f28f125..662d405b3 100644 --- a/test/unit-tests/ExpectationBuilder.cpp +++ b/test/unit-tests/ExpectationBuilder.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include using namespace mimicpp; @@ -280,6 +280,39 @@ TEST_CASE( } } +TEST_CASE( + "ScopedExpectation forwards source_location to finalize.", + "[expectation][expectation::builder]" +) +{ + namespace Matches = Catch::Matchers; + + using SignatureT = void(); + using TimesT = TimesFake; + using FinalizerT = FinalizerFake; + using BaseBuilderT = BasicExpectationBuilder; + + const auto collection = std::make_shared>(); + + const std::source_location beforeLoc = std::source_location::current(); + ScopedExpectation expectation = BaseBuilderT{ + collection, + TimesT{.isSatisfied = true}, + FinalizerT{}, + std::tuple{} + }; + + const auto& inner = dynamic_cast&>( + expectation.expectation()); + REQUIRE_THAT( + inner.from().file_name(), + Matches::Equals(beforeLoc.file_name())); + REQUIRE_THAT( + inner.from().function_name(), + Matches::Equals(beforeLoc.function_name())); + REQUIRE(inner.from().line() == beforeLoc.line() + 1); +} + TEST_CASE( "MIMICPP_SCOPED_EXPECTATION ScopedExpectation with unique name from a builder.", "[expectation][expectation::builder]" From 411eaeccf2f291f0dcdd467547eda3c72a3b781e Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 15:15:06 +0200 Subject: [PATCH 65/68] feat: add print overload for std::source_location --- include/mimic++/Printer.hpp | 31 ++++++++++++++++++++++++------- test/unit-tests/Printer.cpp | 9 +++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/include/mimic++/Printer.hpp b/include/mimic++/Printer.hpp index 1690241e4..3672b2da6 100644 --- a/include/mimic++/Printer.hpp +++ b/include/mimic++/Printer.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -89,7 +90,7 @@ namespace mimicpp::detail OutIter print( OutIter out, T&& value, - const priority_tag<4> + const priority_tag<5> ) requires requires { @@ -103,7 +104,7 @@ namespace mimicpp::detail OutIter print( OutIter out, String&& str, - priority_tag<3> + priority_tag<4> ) { return format::format_to( @@ -116,7 +117,7 @@ namespace mimicpp::detail OutIter print( OutIter out, Range&& range, - priority_tag<2> + priority_tag<3> ); @@ -168,12 +169,28 @@ namespace mimicpp::detail OutIter print( OutIter out, T& value, - const priority_tag<1> + const priority_tag<2> ) { return format::format_to(out, "{}", value); } + template + OutIter print( + OutIter out, + const std::source_location& loc, + const priority_tag<1> + ) + { + return format::format_to( + out, + "{}[{}:{}], {}", + loc.file_name(), + loc.line(), + loc.column(), + loc.function_name()); + } + template OutIter print( OutIter out, @@ -194,7 +211,7 @@ namespace mimicpp::detail ) const { static_assert( - requires(const priority_tag<4> tag) + requires(const priority_tag<5> tag) { { print(out, std::forward(value), tag) } -> std::convertible_to; }, @@ -203,7 +220,7 @@ namespace mimicpp::detail return print( out, std::forward(value), - priority_tag<4>{}); + priority_tag<5>{}); } template @@ -221,7 +238,7 @@ namespace mimicpp::detail OutIter print( OutIter out, Range&& range, - const priority_tag<2> + const priority_tag<3> ) { out = format::format_to(out, "{{ "); diff --git a/test/unit-tests/Printer.cpp b/test/unit-tests/Printer.cpp index 0d3a39ece..167fc7deb 100644 --- a/test/unit-tests/Printer.cpp +++ b/test/unit-tests/Printer.cpp @@ -242,6 +242,15 @@ TEST_CASE( } } + SECTION("std::source_location has specialized printer.") + { + const std::source_location loc = std::source_location::current(); + + REQUIRE_THAT( + mimicpp::print(loc), + Catch::Matchers::Matches(".+\\[\\d+:\\d+\\], .+")); + } + SECTION("When nothing matches, a default token is inserted.") { constexpr NonPrintable value{}; From 9556fe83471ca24f7636e30d75da7704eb46659b Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 15:29:02 +0200 Subject: [PATCH 66/68] feat: ExpectationReport and MatchReport contain source location --- include/mimic++/Expectation.hpp | 2 + include/mimic++/Reporter.hpp | 47 ++++++++++-- test/unit-tests/Reporter.cpp | 125 ++++++++++++++++++++++++++------ 3 files changed, 144 insertions(+), 30 deletions(-) diff --git a/include/mimic++/Expectation.hpp b/include/mimic++/Expectation.hpp index 1fb1c6f4a..89d10b655 100644 --- a/include/mimic++/Expectation.hpp +++ b/include/mimic++/Expectation.hpp @@ -250,6 +250,7 @@ namespace mimicpp ExpectationReport report() const override { return ExpectationReport{ + .sourceLocation = m_SourceLocation, .finalizerDescription = std::nullopt, .timesDescription = m_Times.describe_state(), .expectationDescriptions = std::apply( @@ -279,6 +280,7 @@ namespace mimicpp MatchReport matches(const CallInfoT& call) const override { return MatchReport{ + .sourceLocation = m_SourceLocation, .finalizeReport = {std::nullopt}, .timesReport = MatchReport::Times{ .isApplicable = m_Times.is_applicable(), diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 22ec0edd5..926829cc0 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -121,11 +121,8 @@ namespace mimicpp StringStreamT out{}; format_to( std::ostreambuf_iterator{out}, - "call from {}[{}:{}], {}\n", - report.fromLoc.file_name(), - report.fromLoc.line(), - report.fromLoc.column(), - report.fromLoc.function_name()); + "call from {}\n", + mimicpp::print(report.fromLoc)); format_to( std::ostreambuf_iterator{out}, @@ -164,12 +161,21 @@ namespace mimicpp class ExpectationReport { public: + std::optional sourceLocation{}; std::optional finalizerDescription{}; std::optional timesDescription{}; std::vector> expectationDescriptions{}; [[nodiscard]] - friend bool operator==(const ExpectationReport&, const ExpectationReport&) = default; + friend bool operator ==(const ExpectationReport& lhs, const ExpectationReport& rhs) + { + return lhs.finalizerDescription == rhs.finalizerDescription + && lhs.timesDescription == rhs.timesDescription + && lhs.expectationDescriptions == rhs.expectationDescriptions + && lhs.sourceLocation.has_value() == rhs.sourceLocation.has_value() + && (!lhs.sourceLocation.has_value() + || is_same_source_location(*lhs.sourceLocation, *rhs.sourceLocation)); + } }; /** @@ -185,6 +191,15 @@ namespace mimicpp out << "Expectation report:\n"; + if (report.sourceLocation) + { + out << "from: "; + mimicpp::print( + std::ostreambuf_iterator{out}, + *report.sourceLocation); + out << "\n"; + } + if (report.timesDescription) { format_to( @@ -270,12 +285,21 @@ namespace mimicpp friend bool operator ==(const Expectation&, const Expectation&) = default; }; + std::optional sourceLocation{}; Finalize finalizeReport{}; Times timesReport{}; std::vector expectationReports{}; [[nodiscard]] - friend bool operator ==(const MatchReport&, const MatchReport&) = default; + friend bool operator ==(const MatchReport& lhs, const MatchReport& rhs) + { + return lhs.finalizeReport == rhs.finalizeReport + && lhs.timesReport == rhs.timesReport + && lhs.expectationReports == rhs.expectationReports + && lhs.sourceLocation.has_value() == rhs.sourceLocation.has_value() + && (!lhs.sourceLocation.has_value() + || is_same_source_location(*lhs.sourceLocation, *rhs.sourceLocation)); + } }; /** @@ -352,6 +376,15 @@ namespace mimicpp // GCOVR_EXCL_STOP } + if (report.sourceLocation) + { + out << "from: "; + mimicpp::print( + std::ostreambuf_iterator{out}, + *report.sourceLocation); + out << "\n"; + } + if (!std::ranges::empty(unmatchedExpectationDescriptions)) { out << "failed:\n"; diff --git a/test/unit-tests/Reporter.cpp b/test/unit-tests/Reporter.cpp index 32a142ad2..9205636ae 100644 --- a/test/unit-tests/Reporter.cpp +++ b/test/unit-tests/Reporter.cpp @@ -221,6 +221,7 @@ TEST_CASE( ) { const ExpectationReport first{ + .sourceLocation = std::source_location::current(), .finalizerDescription = "finalizer description", .timesDescription = "times description", .expectationDescriptions = { @@ -238,6 +239,20 @@ TEST_CASE( REQUIRE(!(second!= first)); } + SECTION("When source-location differs, reports do not compare equal.") + { + ExpectationReport second{first}; + second.sourceLocation = GENERATE( + as>{}, + std::nullopt, + std::source_location::current()); + + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + REQUIRE(first != second); + REQUIRE(second!= first); + } + SECTION("When finalizer description differs, reports do not compare equal.") { ExpectationReport second{first}; @@ -363,6 +378,7 @@ TEST_CASE( ) { const MatchReport first{ + .sourceLocation = std::source_location::current(), .finalizeReport = {"finalize description"}, .timesReport = {true, "times description"}, .expectationReports = { @@ -380,6 +396,20 @@ TEST_CASE( REQUIRE(!(second != first)); } + SECTION("When source-location differs, reports do not compare equal.") + { + MatchReport second{first}; + second.sourceLocation = GENERATE( + as>{}, + std::nullopt, + std::source_location::current()); + + REQUIRE(!(first == second)); + REQUIRE(!(second == first)); + REQUIRE(first != second); + REQUIRE(second!= first); + } + SECTION("When finalize report differs, they do not compare equal.") { MatchReport second{first}; @@ -718,6 +748,7 @@ TEST_CASE( SECTION("Without any requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {true, "finalize description"}, .expectationReports = {} @@ -725,14 +756,16 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Matched expectation: {\n" - "}\n")); + Matches::Matches( + "Matched expectation: \\{\n" + "from: .+\\[\\d+:\\d+\\], .+\n" + "\\}\n")); } SECTION("When contains requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {true, "finalize description"}, .expectationReports = { @@ -743,12 +776,13 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Matched expectation: {\n" + Matches::Matches( + "Matched expectation: \\{\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "passed:\n" "\tRequirement1 description,\n" "\tRequirement2 description,\n" - "}\n")); + "\\}\n")); } } @@ -757,6 +791,7 @@ TEST_CASE( SECTION("Without any requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {false, "finalize description"}, .expectationReports = {} @@ -764,15 +799,17 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Inapplicable, but otherwise matched expectation: {\n" + Matches::Matches( + "Inapplicable, but otherwise matched expectation: \\{\n" "reason: finalize description\n" - "}\n")); + "from: .+\\[\\d+:\\d+\\], .+\n" + "\\}\n")); } SECTION("When contains requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {false, "finalize description"}, .expectationReports = { @@ -783,13 +820,14 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Inapplicable, but otherwise matched expectation: {\n" + Matches::Matches( + "Inapplicable, but otherwise matched expectation: \\{\n" "reason: finalize description\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "passed:\n" "\tRequirement1 description,\n" "\tRequirement2 description,\n" - "}\n")); + "\\}\n")); } } @@ -798,6 +836,7 @@ TEST_CASE( SECTION("When contains only failed requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {true, "finalize description"}, .expectationReports = { @@ -808,17 +847,19 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Unmatched expectation: {\n" + Matches::Matches( + "Unmatched expectation: \\{\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "failed:\n" "\tRequirement1 description,\n" "\tRequirement2 description,\n" - "}\n")); + "\\}\n")); } SECTION("When contains only mixed requirements.") { const MatchReport report{ + .sourceLocation = std::source_location::current(), .finalizeReport = {}, .timesReport = {true, "finalize description"}, .expectationReports = { @@ -829,15 +870,32 @@ TEST_CASE( REQUIRE_THAT( stringify_match_report(report), - Matches::Equals( - "Unmatched expectation: {\n" + Matches::Matches( + "Unmatched expectation: \\{\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "failed:\n" "\tRequirement2 description,\n" "passed:\n" "\tRequirement1 description,\n" - "}\n")); + "\\}\n")); } } + + SECTION("When source location is empty, that information is omitted.") + { + const MatchReport report{ + .sourceLocation = std::nullopt, + .finalizeReport = {}, + .timesReport = {true, "finalize description"}, + .expectationReports = {} + }; + + REQUIRE_THAT( + stringify_match_report(report), + Matches::Matches( + "Matched expectation: \\{\n" + "\\}\n")); + } } TEST_CASE( @@ -899,6 +957,7 @@ TEST_CASE( namespace Matches = Catch::Matchers; ExpectationReport report{ + .sourceLocation = std::source_location::current(), .finalizerDescription = "finalizer description", .timesDescription = "times description", .expectationDescriptions = { @@ -910,8 +969,9 @@ TEST_CASE( { REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), - Matches::Equals( + Matches::Matches( "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "times: times description\n" "expects:\n" "\texpectation1 description,\n" @@ -924,8 +984,9 @@ TEST_CASE( REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), - Matches::Equals( + Matches::Matches( "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "expects:\n" "\texpectation1 description,\n" "finally: finalizer description\n")); @@ -937,8 +998,9 @@ TEST_CASE( REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), - Matches::Equals( + Matches::Matches( "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "times: times description\n" "expects:\n" @@ -951,8 +1013,9 @@ TEST_CASE( REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), - Matches::Equals( + Matches::Matches( "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "times: times description\n" "finally: finalizer description\n")); } @@ -963,8 +1026,9 @@ TEST_CASE( REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), - Matches::Equals( + Matches::Matches( "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" "times: times description\n" "finally: finalizer description\n")); } @@ -973,6 +1037,21 @@ TEST_CASE( { report.expectationDescriptions.emplace_back(std::nullopt); + REQUIRE_THAT( + stringify_expectation_report(std::as_const(report)), + Matches::Matches( + "Expectation report:\n" + "from: .+\\[\\d+:\\d+\\], .+\n" + "times: times description\n" + "expects:\n" + "\texpectation1 description,\n" + "finally: finalizer description\n")); + } + + SECTION("When expectatin contains no source-location.") + { + report.sourceLocation.reset(); + REQUIRE_THAT( stringify_expectation_report(std::as_const(report)), Matches::Equals( From 219ef13fb338f0e1dc473eed51fe3ef431b4235f Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 15:51:10 +0200 Subject: [PATCH 67/68] docs: tweak reporter documentation --- include/mimic++/Reporter.hpp | 44 ++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/include/mimic++/Reporter.hpp b/include/mimic++/Reporter.hpp index 926829cc0..f06533e2f 100644 --- a/include/mimic++/Reporter.hpp +++ b/include/mimic++/Reporter.hpp @@ -25,21 +25,14 @@ namespace mimicpp { /** - * \defgroup REPORTING reporting - * \brief Contains reporting related symbols - * \details Reports are simplified object representations of ``mimicpp`` types. In fact, reports are used to communicate with + * \defgroup REPORTING_REPORTS reports + * \ingroup REPORTING + * \brief Contains reports of ``mimic++`` types. + * \details Reports are simplified object representations of ``mimic++`` types. In fact, reports are used to communicate with * independent domains (e.g. unit-test frameworks) over the ``IReporter`` interface and are thus designed to provide as much * transparent information as possible, without requiring them to be a generic type. * - * At any time there exists exactly one global reporter, which may be directly or indirectly exchanged by users. - * Reports are sent to the currently installed reporter via the ``report_xyz`` free-functions. Most of those functions require, that - * reports are handled in a specific manner (e.g. ``report_no_matches`` is expected to never return) and custom reporters **must** - * follow that specification, otherwise this will lead to undefined behavior. For further details, have a look at the specific - * function documentation. - * - * \note In general users shall not directly interact with the installed reporter, except when they want to replace it. - * - *\{ + * \{ */ /** @@ -414,6 +407,33 @@ namespace mimicpp return std::move(out).str(); } + /** + * \} + */ + + /** + * \defgroup REPORTING reporting + * \brief Contains reporting related symbols + * \details Reporting is executed, when something notably has been detected by ``mimic++``; often it is expected, that the reporter + * reacts to such a report in a specific manner (e.g. aborting the test case). For example the ``DefaultReporter`` simply throws + * exceptions on error reports, while other more specialized reporters handle such cases slightly different (but still abort the + * current test). + * These specialized Reporters are used to send reports to a specific destination (e.g. the utilized unit-test framework), + * which often provide more advanced mechanics for printing failed tests to the users. + * + * Users may provide their own reporter implementation; e.g. if there is no reporter for the desired unit-test framework. + * + * At any time there exists exactly one global reporter, which may be directly or indirectly exchanged by users. + * Reports are sent to the currently installed reporter via the ``report_xyz`` free-functions. Most of those functions require, that + * reports are handled in a specific manner (e.g. ``report_no_matches`` is expected to never return) and custom reporters **must** + * follow that specification, otherwise this will lead to undefined behavior. For further details, have a look at the specific + * function documentation. + * + * \note In general users shall not directly interact with the installed reporter, except when they want to replace it. + * + * \{ + */ + /** * \brief The reporter interface. * \details This is the central interface to be used, when creating reporters for external domains. From 26168e61aa2db4e869243ccf27dbbe0b6b58d7ad Mon Sep 17 00:00:00 2001 From: DNKpp Date: Tue, 7 May 2024 15:57:40 +0200 Subject: [PATCH 68/68] fix: make test more robust --- test/unit-tests/ExpectationBuilder.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/ExpectationBuilder.cpp b/test/unit-tests/ExpectationBuilder.cpp index 662d405b3..d7f5c3b75 100644 --- a/test/unit-tests/ExpectationBuilder.cpp +++ b/test/unit-tests/ExpectationBuilder.cpp @@ -301,6 +301,7 @@ TEST_CASE( FinalizerT{}, std::tuple{} }; + const std::source_location afterLoc = std::source_location::current(); const auto& inner = dynamic_cast&>( expectation.expectation()); @@ -310,7 +311,8 @@ TEST_CASE( REQUIRE_THAT( inner.from().function_name(), Matches::Equals(beforeLoc.function_name())); - REQUIRE(inner.from().line() == beforeLoc.line() + 1); + REQUIRE(beforeLoc.line() < inner.from().line()); + REQUIRE(inner.from().line() < afterLoc.line()); } TEST_CASE(