mimic++ v4
Loading...
Searching...
No Matches
Expectation.hpp
Go to the documentation of this file.
1// // Copyright Dominic (DNKpp) Koepke 2024 - 2024.
2// // Distributed under the Boost Software License, Version 1.0.
3// // (See accompanying file LICENSE_1_0.txt or copy at
4// // https://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef MIMICPP_EXPECTATION_HPP
7#define MIMICPP_EXPECTATION_HPP
8
9#pragma once
10
11#include "mimic++/Call.hpp"
13#include "mimic++/Reporter.hpp"
15
16#include <cassert>
17#include <concepts>
18#include <memory>
19#include <mutex>
20#include <ranges>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25namespace mimicpp::detail
26{
27 template <typename Return, typename... Params, typename Signature>
28 std::optional<MatchReport> make_match_report(
29 const call::Info<Return, Params...>& call,
30 const Expectation<Signature>& expectation
31 ) noexcept
32 {
33 try
34 {
35 return expectation.matches(call);
36 }
37 catch (...)
38 {
39 report_unhandled_exception(
40 make_call_report(call),
41 expectation.report(),
42 std::current_exception());
43 }
44
45 return std::nullopt;
46 }
47
48 template <typename Signature>
49 constexpr auto pick_best_match(std::vector<std::tuple<Expectation<Signature>&, MatchReport>>& matches)
50 {
51 constexpr auto ratings = [](const auto& el) noexcept -> const auto& {
52 return std::get<state_applicable>(
53 std::get<MatchReport>(el).controlReport)
54 .sequenceRatings;
55 };
56
57 auto best = std::ranges::begin(matches);
58 for (auto iter = best + 1;
59 iter != std::ranges::end(matches);
60 ++iter)
61 {
62 if (!sequence::detail::has_better_rating(
63 ratings(*best),
64 ratings(*iter)))
65 {
66 best = iter;
67 }
68 }
69
70 return best;
71 }
72}
73
74namespace mimicpp
75{
104 template <typename Signature>
105 requires std::same_as<Signature, signature_decay_t<Signature>>
106 class Expectation
107 {
108 public:
113
118
122 virtual ~Expectation() = default;
123
127 [[nodiscard]]
128 Expectation() = default;
129
133 Expectation(const Expectation&) = delete;
134
139
144
149
154 [[nodiscard]]
155 virtual ExpectationReport report() const = 0;
156
161 [[nodiscard]]
162 virtual bool is_satisfied() const noexcept = 0;
163
169 [[nodiscard]]
170 virtual MatchReport matches(const CallInfoT& call) const = 0;
171
177 virtual void consume(const CallInfoT& call) = 0;
178
185 [[nodiscard]]
186 virtual constexpr ReturnT finalize_call(const CallInfoT& call) = 0;
187
192 [[nodiscard]]
193 virtual constexpr const std::source_location& from() const noexcept = 0;
194 };
195
200 template <typename Signature>
201 requires std::same_as<Signature, signature_decay_t<Signature>>
203 {
204 public:
209
214
219
224
228 [[nodiscard]]
230
235
240
244 [[nodiscard]]
246
251
258 void push(std::shared_ptr<ExpectationT> expectation)
259 {
260 const std::scoped_lock lock{m_ExpectationsMx};
261
262 assert(
263 std::ranges::find(m_Expectations, expectation) == std::ranges::end(m_Expectations)
264 && "Expectation already belongs to this storage.");
265
266 m_Expectations.emplace_back(std::move(expectation));
267 }
268
276 void remove(std::shared_ptr<ExpectationT> expectation)
277 {
278 const std::scoped_lock lock{m_ExpectationsMx};
279
280 auto iter = std::ranges::find(m_Expectations, expectation);
281 assert(iter != std::ranges::end(m_Expectations) && "Expectation does not belong to this storage.");
282 m_Expectations.erase(iter);
283
284 if (!expectation->is_satisfied())
285 {
286 detail::report_unfulfilled_expectation(
287 expectation->report());
288 }
289 }
290
300 [[nodiscard]]
302 {
303 std::vector<std::tuple<ExpectationT&, MatchReport>> matches{};
304 std::vector<MatchReport> noMatches{};
305 std::vector<MatchReport> inapplicableMatches{};
306
307 for (const std::scoped_lock lock{m_ExpectationsMx};
308 auto& exp : m_Expectations | std::views::reverse)
309 {
310 if (std::optional matchReport = detail::make_match_report(call, *exp))
311 {
312 switch (evaluate_match_report(*matchReport))
313 {
314 using enum MatchResult;
315 case none:
316 noMatches.emplace_back(*std::move(matchReport));
317 break;
318 case inapplicable:
319 inapplicableMatches.emplace_back(*std::move(matchReport));
320 break;
321 case full:
322 matches.emplace_back(*exp, *std::move(matchReport));
323 break;
324 // GCOVR_EXCL_START
325 default:
326 unreachable();
327 // GCOVR_EXCL_STOP
328 }
329 }
330 }
331
332 if (!std::ranges::empty(matches))
333 {
334 auto&& [exp, report] = *detail::pick_best_match(matches);
335 detail::report_full_match(
336 make_call_report(call),
337 std::move(report));
338 exp.consume(call);
339 return exp.finalize_call(call);
340 }
341
342 if (!std::ranges::empty(inapplicableMatches))
343 {
344 detail::report_inapplicable_matches(
345 make_call_report(call),
346 std::move(inapplicableMatches));
347 }
348
349 detail::report_no_matches(
350 make_call_report(call),
351 std::move(noMatches));
352 }
353
354 private:
355 std::vector<std::shared_ptr<ExpectationT>> m_Expectations{};
356 std::mutex m_ExpectationsMx{};
357 };
358
362 template <typename T, typename Signature>
363 concept expectation_policy_for = std::is_move_constructible_v<T>
364 && std::is_destructible_v<T>
365 && std::same_as<T, std::remove_cvref_t<T>>
366 && requires(T& policy, const call::info_for_signature_t<Signature>& info)
367 {
368 { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>;
369 { std::as_const(policy).matches(info) } -> std::convertible_to<bool>;
370 { std::as_const(policy).describe() } -> std::convertible_to<std::optional<StringT>>;
371 { policy.consume(info) };
372 };
373
377 template <typename T, typename Signature>
378 concept finalize_policy_for = std::is_move_constructible_v<T>
379 && std::is_destructible_v<T>
380 && std::same_as<T, std::remove_cvref_t<T>>
381 && requires(T& policy, const call::info_for_signature_t<Signature>& info)
382 {
383 { policy.finalize_call(info) } -> std::convertible_to<signature_return_type_t<Signature>>;
384 };
385
389 template <typename T>
390 concept control_policy = std::is_move_constructible_v<T>
391 && std::is_destructible_v<T>
392 && std::same_as<T, std::remove_cvref_t<T>>
393 && requires(T& policy)
394 {
395 { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>;
396 { std::as_const(policy).state() } -> std::convertible_to<control_state_t>;
397 policy.consume();
398 };
399
407 template <
408 typename Signature,
410 finalize_policy_for<Signature> FinalizePolicy,
412 >
414 : public Expectation<Signature>
415 {
416 public:
418 using FinalizerT = FinalizePolicy;
419 using PolicyListT = std::tuple<Policies...>;
422
433 template <typename ControlPolicyArg, typename FinalizerArg, typename... PolicyArgs>
434 requires std::constructible_from<ControlPolicyT, ControlPolicyArg>
435 && std::constructible_from<FinalizerT, FinalizerArg>
436 && std::constructible_from<PolicyListT, PolicyArgs...>
437 constexpr explicit BasicExpectation(
438 const std::source_location& sourceLocation,
439 ControlPolicyArg&& controlArg,
440 FinalizerArg&& finalizerArg,
441 PolicyArgs&&... args
442 ) noexcept(
443 std::is_nothrow_constructible_v<ControlPolicyT, ControlPolicyArg>
444 && std::is_nothrow_constructible_v<FinalizerT, FinalizerArg>
445 && (std::is_nothrow_constructible_v<Policies, PolicyArgs> && ...))
446 : m_SourceLocation{sourceLocation},
447 m_ControlPolicy{std::forward<ControlPolicyArg>(controlArg)},
448 m_Policies{std::forward<PolicyArgs>(args)...},
449 m_Finalizer{std::forward<FinalizerArg>(finalizerArg)}
450 {
451 }
452
456 [[nodiscard]]
457 ExpectationReport report() const override
458 {
459 return ExpectationReport{
460 .sourceLocation = m_SourceLocation,
461 .finalizerDescription = std::nullopt,
462 .timesDescription = std::invoke(
463 [this]
464 {
465 StringStreamT ss{};
466 std::visit(
467 std::bind_front(
468 detail::control_state_printer{},
469 std::ostreambuf_iterator{ss}),
470 m_ControlPolicy.state());
471 return std::move(ss).str();
472 }),
473 .expectationDescriptions = std::apply(
474 [&](const auto&... policies)
475 {
476 return std::vector<std::optional<StringT>>{
477 policies.describe()...
478 };
479 },
480 m_Policies)
481 };
482 }
483
487 [[nodiscard]]
488 constexpr bool is_satisfied() const noexcept override
489 {
490 return m_ControlPolicy.is_satisfied()
491 && std::apply(
492 [](const auto&... policies) noexcept
493 {
494 return (... && policies.is_satisfied());
495 },
496 m_Policies);
497 }
498
502 [[nodiscard]]
503 MatchReport matches(const CallInfoT& call) const override
504 {
505 return MatchReport{
506 .sourceLocation = m_SourceLocation,
507 .finalizeReport = {std::nullopt},
508 .controlReport = m_ControlPolicy.state(),
509 .expectationReports = std::apply(
510 [&](const auto&... policies)
511 {
512 return std::vector<MatchReport::Expectation>{
514 .isMatching = policies.matches(call),
515 .description = policies.describe()
516 }...
517 };
518 },
519 m_Policies)
520 };
521 }
522
526 constexpr void consume(const CallInfoT& call) override
527 {
528 m_ControlPolicy.consume();
529 std::apply(
530 [&](auto&... policies) noexcept
531 {
532 (..., policies.consume(call));
533 },
534 m_Policies);
535 }
536
540 [[nodiscard]]
541 constexpr ReturnT finalize_call(const CallInfoT& call) override
542 {
543 return m_Finalizer.finalize_call(call);
544 }
545
549 [[nodiscard]]
550 constexpr const std::source_location& from() const noexcept override
551 {
552 return m_SourceLocation;
553 }
554
555 private:
556 std::source_location m_SourceLocation;
557 ControlPolicyT m_ControlPolicy;
558 PolicyListT m_Policies;
559 [[no_unique_address]] FinalizerT m_Finalizer{};
560 };
561
568 {
569 private:
570 class Concept
571 {
572 public:
573 virtual ~Concept() noexcept(false)
574 {
575 }
576
577 Concept(const Concept&) = delete;
578 Concept& operator =(const Concept&) = delete;
579 Concept(Concept&&) = delete;
580 Concept& operator =(Concept&&) = delete;
581
582 [[nodiscard]]
583 virtual bool is_satisfied() const = 0;
584
585 [[nodiscard]]
586 virtual const std::source_location& from() const noexcept = 0;
587
588 protected:
589 Concept() = default;
590 };
591
592 template <typename Signature>
593 class Model final
594 : public Concept
595 {
596 public:
597 using StorageT = ExpectationCollection<Signature>;
598 using ExpectationT = Expectation<Signature>;
599
600 ~Model() noexcept(false) override
601 {
602 m_Storage->remove(m_Expectation);
603 }
604
605 [[nodiscard]]
606 explicit Model(
607 std::shared_ptr<StorageT>&& storage,
608 std::shared_ptr<ExpectationT>&& expectation
609 ) noexcept
610 : m_Storage{std::move(storage)},
611 m_Expectation{std::move(expectation)}
612 {
613 assert(m_Storage && "Storage is nullptr.");
614 assert(m_Expectation && "Expectation is nullptr.");
615
616 m_Storage->push(m_Expectation);
617 }
618
619 [[nodiscard]]
620 bool is_satisfied() const override
621 {
622 return m_Expectation->is_satisfied();
623 }
624
625 [[nodiscard]]
626 const std::source_location& from() const noexcept override
627 {
628 return m_Expectation->from();
629 }
630
631 private:
632 std::shared_ptr<StorageT> m_Storage;
633 std::shared_ptr<ExpectationT> m_Expectation;
634 };
635
636 public:
641 ~ScopedExpectation() noexcept(false) // NOLINT(modernize-use-equals-default)
642 {
643 // we must call the dtor manually here, because std::unique_ptr's dtor mustn't throw.
644 delete m_Inner.release();
645 }
646
653 template <typename Signature>
654 [[nodiscard]]
656 std::shared_ptr<ExpectationCollection<Signature>> collection,
657 std::shared_ptr<typename ExpectationCollection<Signature>::ExpectationT> expectation
658 ) noexcept
659 : m_Inner{
660 std::make_unique<Model<Signature>>(
661 std::move(collection),
662 std::move(expectation))
663 }
664 {
665 }
666
673 template <typename T>
674 requires requires(const std::source_location& loc)
675 {
676 { std::declval<T&&>().finalize(loc) } -> std::convertible_to<ScopedExpectation>;
677 }
678 [[nodiscard]]
679 explicit(false) constexpr ScopedExpectation(T&& object, const std::source_location& loc = std::source_location::current())
680 : ScopedExpectation{std::forward<T>(object).finalize(loc)}
681 {
682 }
683
687 ScopedExpectation(const ScopedExpectation&) = delete;
688
692 ScopedExpectation& operator =(const ScopedExpectation&) = delete;
693
697 [[nodiscard]]
699
703 ScopedExpectation& operator =(ScopedExpectation&&) = default;
704
709 [[nodiscard]]
710 bool is_satisfied() const
711 {
712 return m_Inner->is_satisfied();
713 }
714
719 [[nodiscard]]
720 const std::source_location& from() const noexcept
721 {
722 return m_Inner->from();
723 }
724
725 private:
726 std::unique_ptr<Concept> m_Inner;
727 };
728
732}
733
734#endif
The actual expectation template.
Definition Expectation.hpp:415
FinalizePolicy FinalizerT
Definition Expectation.hpp:418
constexpr void consume(const CallInfoT &call) override
Informs all policies, that the given call has been accepted.
Definition Expectation.hpp:526
constexpr BasicExpectation(const std::source_location &sourceLocation, ControlPolicyArg &&controlArg, FinalizerArg &&finalizerArg, PolicyArgs &&... args) noexcept(std::is_nothrow_constructible_v< ControlPolicyT, ControlPolicyArg > &&std::is_nothrow_constructible_v< FinalizerT, FinalizerArg > &&(std::is_nothrow_constructible_v< Policies, PolicyArgs > &&...))
Constructs the expectation with the given arguments.
Definition Expectation.hpp:437
constexpr bool is_satisfied() const noexcept override
Queries all policies, whether they are satisfied.
Definition Expectation.hpp:488
typename Expectation< Signature >::ReturnT ReturnT
Definition Expectation.hpp:421
constexpr ReturnT finalize_call(const CallInfoT &call) override
Requests the given call to be finalized.
Definition Expectation.hpp:541
ExpectationReport report() const override
Creates a report of the internal state.
Definition Expectation.hpp:457
call::info_for_signature_t< Signature > CallInfoT
Definition Expectation.hpp:420
MatchReport matches(const CallInfoT &call) const override
Queries all policies, whether they accept the given call.
Definition Expectation.hpp:503
std::tuple< Policies... > PolicyListT
Definition Expectation.hpp:419
constexpr const std::source_location & from() const noexcept override
Returns the source-location, where this expectation has been created.
Definition Expectation.hpp:550
Definition ControlPolicy.hpp:165
Collects all expectations for a specific (decayed) signature.
Definition Fwd.hpp:176
ExpectationCollection(const ExpectationCollection &)=delete
Deleted copy-constructor.
~ExpectationCollection()=default
Defaulted destructor.
signature_return_type_t< Signature > ReturnT
The return type.
Definition Expectation.hpp:218
ReturnT handle_call(const CallInfoT &call)
Handles the incoming call.
Definition Expectation.hpp:301
ExpectationCollection(ExpectationCollection &&)=default
Defaulted move-constructor.
void push(std::shared_ptr< ExpectationT > expectation)
Inserts the given expectation into the internal storage.
Definition Expectation.hpp:258
ExpectationCollection()=default
Defaulted default constructor.
call::info_for_signature_t< Signature > CallInfoT
The expected call type.
Definition Expectation.hpp:208
void remove(std::shared_ptr< ExpectationT > expectation)
Removes the given expectation from the internal storage.
Definition Expectation.hpp:276
The base interface for expectations.
Definition Fwd.hpp:172
Expectation(Expectation &&)=delete
Deleted move-constructor.
call::info_for_signature_t< Signature > CallInfoT
The expected call type.
Definition Expectation.hpp:112
virtual bool is_satisfied() const noexcept=0
Queries all policies, whether they are satisfied.
virtual constexpr const std::source_location & from() const noexcept=0
Returns the source-location, where this expectation has been created.
virtual ExpectationReport report() const =0
Creates a report of the internal state.
virtual ~Expectation()=default
Defaulted virtual destructor.
signature_return_type_t< Signature > ReturnT
The return type.
Definition Expectation.hpp:117
Expectation()=default
Defaulted default constructor.
Expectation(const Expectation &)=delete
Deleted copy-constructor.
virtual constexpr ReturnT finalize_call(const CallInfoT &call)=0
Requests the given call to be finalized.
Expectation & operator=(const Expectation &)=delete
Deleted copy-assignment-operator.
virtual void consume(const CallInfoT &call)=0
Informs all policies, that the given call has been accepted.
Contains the extracted info from a typed expectation.
Definition Reports.hpp:320
Information a used expectation policy.
Definition Reports.hpp:426
Contains the detailed information for match outcomes.
Definition Reports.hpp:407
Takes the ownership of an expectation and check whether it's satisfied during destruction.
Definition Expectation.hpp:568
bool is_satisfied() const
Queries the stored expectation, whether it's satisfied.
Definition Expectation.hpp:710
ScopedExpectation(ScopedExpectation &&)=default
Defaulted move-constructor.
const std::source_location & from() const noexcept
Queries the stored expectation for it's stored source-location.
Definition Expectation.hpp:720
ScopedExpectation(std::shared_ptr< ExpectationCollection< Signature > > collection, std::shared_ptr< typename ExpectationCollection< Signature >::ExpectationT > expectation) noexcept
Constructor, which generates the type-erase storage.
Definition Expectation.hpp:655
~ScopedExpectation() noexcept(false)
Removes the owned expectation from the ExpectationCollection and checks, whether it's satisfied.
Definition Expectation.hpp:641
Determines, whether the given type satisfies the requirements of a control-policy.
Definition Expectation.hpp:390
Determines, whether the given type satisfies the requirements of an expectation-policy for the given ...
Definition Expectation.hpp:363
Determines, whether the given type satisfies the requirements of a finalize-policy for the given sign...
Definition Expectation.hpp:378
MatchResult evaluate_match_report(const MatchReport &report) noexcept
Determines, whether a match report actually denotes a full, inapplicable or no match.
Definition Reports.hpp:458
CallReport make_call_report(const call::Info< Return, Params... > &callInfo)
Generates the call report for a given call info.
Definition Reports.hpp:244
typename signature_decay< Signature >::type signature_decay_t
Convenience alias, exposing the type member alias of the actual type-trait.
Definition Fwd.hpp:68
typename signature_return_type< Signature >::type signature_return_type_t
Convenience alias, exposing the type member alias of the actual type-trait.
Definition Fwd.hpp:84
typename info_for_signature< Signature >::type info_for_signature_t
Definition Call.hpp:70
Definition BoostTest.hpp:20
MatchResult
Definition Fwd.hpp:181
void unreachable()
Invokes undefined behavior.
Definition Utility.hpp:93
std::basic_ostringstream< CharT, CharTraitsT > StringStreamT
Definition Printer.hpp:41