mimic++ v6
Loading...
Searching...
No Matches
Expectation.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2025.
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"
12#include "mimic++/Reporter.hpp"
13#include "mimic++/Sequence.hpp"
15
16#include <cassert>
17#include <concepts>
18#include <functional>
19#include <memory>
20#include <mutex>
21#include <optional>
22#include <ranges>
23#include <tuple>
24#include <utility>
25#include <vector>
26
27namespace mimicpp::detail
28{
29 template <typename Return, typename... Params, typename Signature>
30 std::optional<MatchReport> make_match_report(
31 const call::Info<Return, Params...>& call,
32 const Expectation<Signature>& expectation) noexcept
33 {
34 try
35 {
36 return expectation.matches(call);
37 }
38 catch (...)
39 {
40 report_unhandled_exception(
41 make_call_report(call),
42 expectation.report(),
43 std::current_exception());
44 }
45
46 return std::nullopt;
47 }
48
49 template <typename Signature>
50 constexpr auto pick_best_match(std::vector<std::tuple<Expectation<Signature>&, MatchReport>>& matches)
51 {
52 constexpr auto ratings = [](const auto& el) noexcept -> const auto& {
53 return std::get<state_applicable>(
54 std::get<MatchReport>(el).controlReport)
55 .sequenceRatings;
56 };
57
58 auto best = std::ranges::begin(matches);
59 for (auto iter = best + 1;
60 iter != std::ranges::end(matches);
61 ++iter)
62 {
63 if (!sequence::detail::has_better_rating(
64 ratings(*best),
65 ratings(*iter)))
66 {
67 best = iter;
68 }
69 }
70
71 return best;
72 }
73}
74
75namespace mimicpp
76{
100
105 template <typename Signature>
106 requires std::same_as<Signature, signature_decay_t<Signature>>
107 class Expectation
108 {
109 public:
114
119
123 virtual ~Expectation() = default;
124
128 [[nodiscard]]
129 Expectation() = default;
130
134 Expectation(const Expectation&) = delete;
135
140
145
150
155 [[nodiscard]]
156 virtual ExpectationReport report() const = 0;
157
162 [[nodiscard]]
163 virtual bool is_satisfied() const noexcept = 0;
164
170 [[nodiscard]]
171 virtual MatchReport matches(const CallInfoT& call) const = 0;
172
178 virtual void consume(const CallInfoT& call) = 0;
179
186 [[nodiscard]]
187 virtual constexpr ReturnT finalize_call(const CallInfoT& call) = 0;
188
193 [[nodiscard]]
194 virtual constexpr const std::source_location& from() const noexcept = 0;
195 };
196
201 template <typename Signature>
202 requires std::same_as<Signature, signature_decay_t<Signature>>
204 {
205 public:
210
215
220
225
229 [[nodiscard]]
231
236
241
245 [[nodiscard]]
247
252
259 void push(std::shared_ptr<ExpectationT> expectation)
260 {
261 const std::scoped_lock lock{m_ExpectationsMx};
262
263 assert(
264 std::ranges::find(m_Expectations, expectation) == std::ranges::end(m_Expectations)
265 && "Expectation already belongs to this storage.");
266
267 m_Expectations.emplace_back(std::move(expectation));
268 }
269
277 void remove(std::shared_ptr<ExpectationT> expectation)
278 {
279 const std::scoped_lock lock{m_ExpectationsMx};
280
281 auto iter = std::ranges::find(m_Expectations, expectation);
282 assert(iter != std::ranges::end(m_Expectations) && "Expectation does not belong to this storage.");
283 m_Expectations.erase(iter);
284
285 if (!expectation->is_satisfied())
286 {
287 detail::report_unfulfilled_expectation(
288 expectation->report());
289 }
290 }
291
301 [[nodiscard]]
303 {
304 std::vector<std::tuple<ExpectationT&, MatchReport>> matches{};
305 std::vector<MatchReport> noMatches{};
306 std::vector<MatchReport> inapplicableMatches{};
307
308 for (const std::scoped_lock lock{m_ExpectationsMx};
309 auto& exp : m_Expectations | std::views::reverse)
310 {
311 if (std::optional matchReport = detail::make_match_report(call, *exp))
312 {
313 switch (evaluate_match_report(*matchReport))
314 {
315 using enum MatchResult;
316 case none:
317 noMatches.emplace_back(*std::move(matchReport));
318 break;
319 case inapplicable:
320 inapplicableMatches.emplace_back(*std::move(matchReport));
321 break;
322 case full:
323 matches.emplace_back(*exp, *std::move(matchReport));
324 break;
325 // GCOVR_EXCL_START
326 default:
327 unreachable();
328 // GCOVR_EXCL_STOP
329 }
330 }
331 }
332
333 if (!std::ranges::empty(matches))
334 {
335 auto&& [exp, report] = *detail::pick_best_match(matches);
336
337 // Todo: Avoid the call copy
338 // Maybe we can prevent the copy here, but we should keep the instruction order as-is, because
339 // in cases of a throwing finalizer, we might introduce bugs. At least there are some tests, which
340 // will fail if done wrong.
341 detail::report_full_match(
343 std::move(report));
344 exp.consume(call);
345 return exp.finalize_call(call);
346 }
347
348 if (!std::ranges::empty(inapplicableMatches))
349 {
350 detail::report_inapplicable_matches(
351 make_call_report(std::move(call)),
352 std::move(inapplicableMatches));
353 }
354
355 detail::report_no_matches(
356 make_call_report(std::move(call)),
357 std::move(noMatches));
358 }
359
360 private:
361 std::vector<std::shared_ptr<ExpectationT>> m_Expectations{};
362 std::mutex m_ExpectationsMx{};
363 };
364
368 template <typename T, typename Signature>
369 concept expectation_policy_for = std::is_move_constructible_v<T>
370 && std::is_destructible_v<T>
371 && std::same_as<T, std::remove_cvref_t<T>>
372 && requires(T& policy, const call::info_for_signature_t<Signature>& info) {
373 { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>;
374 { std::as_const(policy).matches(info) } -> std::convertible_to<bool>;
375 { std::as_const(policy).describe() } -> std::convertible_to<std::optional<StringT>>;
376 { policy.consume(info) };
377 };
378
382 template <typename T, typename Signature>
383 concept finalize_policy_for = std::is_move_constructible_v<T>
384 && std::is_destructible_v<T>
385 && std::same_as<T, std::remove_cvref_t<T>>
386 && requires(T& policy, const call::info_for_signature_t<Signature>& info) {
387 { policy.finalize_call(info) } -> std::convertible_to<signature_return_type_t<Signature>>;
388 };
389
393 template <typename T>
394 concept control_policy = std::is_move_constructible_v<T>
395 && std::is_destructible_v<T>
396 && std::same_as<T, std::remove_cvref_t<T>>
397 && requires(T& policy) {
398 { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>;
399 { std::as_const(policy).state() } -> std::convertible_to<control_state_t>;
400 policy.consume();
401 };
402
410 template <
411 typename Signature,
413 finalize_policy_for<Signature> FinalizePolicy,
416 : public Expectation<Signature>
417 {
418 public:
420 using FinalizerT = FinalizePolicy;
421 using PolicyListT = std::tuple<Policies...>;
424
435 template <typename ControlPolicyArg, typename FinalizerArg, typename... PolicyArgs>
436 requires std::constructible_from<ControlPolicyT, ControlPolicyArg>
437 && std::constructible_from<FinalizerT, FinalizerArg>
438 && std::constructible_from<PolicyListT, PolicyArgs...>
439 constexpr explicit BasicExpectation(
440 const std::source_location& sourceLocation,
441 ControlPolicyArg&& controlArg,
442 FinalizerArg&& finalizerArg,
443 PolicyArgs&&... args) noexcept(std::is_nothrow_constructible_v<ControlPolicyT, ControlPolicyArg> && std::is_nothrow_constructible_v<FinalizerT, FinalizerArg> && (std::is_nothrow_constructible_v<Policies, PolicyArgs> && ...))
444 : m_SourceLocation{sourceLocation},
445 m_ControlPolicy{std::forward<ControlPolicyArg>(controlArg)},
446 m_Policies{std::forward<PolicyArgs>(args)...},
447 m_Finalizer{std::forward<FinalizerArg>(finalizerArg)}
448 {
449 }
450
454 [[nodiscard]]
455 ExpectationReport report() const override
456 {
457 return ExpectationReport{
458 .sourceLocation = m_SourceLocation,
459 .finalizerDescription = std::nullopt,
460 .timesDescription = describe_times(),
461 .expectationDescriptions = std::apply(
462 [&](const auto&... policies) {
463 return std::vector<std::optional<StringT>>{
464 policies.describe()...};
465 },
466 m_Policies)};
467 }
468
472 [[nodiscard]]
473 constexpr bool is_satisfied() const noexcept override
474 {
475 return m_ControlPolicy.is_satisfied()
476 && std::apply(
477 [](const auto&... policies) noexcept {
478 return (... && policies.is_satisfied());
479 },
480 m_Policies);
481 }
482
486 [[nodiscard]]
487 MatchReport matches(const CallInfoT& call) const override
488 {
489 return MatchReport{
490 .sourceLocation = m_SourceLocation,
491 .finalizeReport = {std::nullopt},
492 .controlReport = m_ControlPolicy.state(),
493 .expectationReports = std::apply(
494 [&](const auto&... policies) {
495 return std::vector<MatchReport::Expectation>{
497 .isMatching = policies.matches(call),
498 .description = policies.describe()}
499 ...
500 };
501 },
502 m_Policies)};
503 }
504
508 constexpr void consume(const CallInfoT& call) override
509 {
510 m_ControlPolicy.consume();
511 std::apply(
512 [&](auto&... policies) noexcept {
513 (..., policies.consume(call));
514 },
515 m_Policies);
516 }
517
521 [[nodiscard]]
522 constexpr ReturnT finalize_call(const CallInfoT& call) override
523 {
524 return m_Finalizer.finalize_call(call);
525 }
526
530 [[nodiscard]]
531 constexpr const std::source_location& from() const noexcept override
532 {
533 return m_SourceLocation;
534 }
535
536 private:
537 std::source_location m_SourceLocation;
538 ControlPolicyT m_ControlPolicy;
539 PolicyListT m_Policies;
540 [[no_unique_address]] FinalizerT m_Finalizer{};
541
542 [[nodiscard]]
543 StringT describe_times() const
544 {
545 StringStreamT ss{};
546 std::visit(
547 std::bind_front(
548 detail::control_state_printer{},
549 std::ostreambuf_iterator{ss}),
550 m_ControlPolicy.state());
551 return std::move(ss).str();
552 }
553 };
554
561 {
562 private:
563 class Concept
564 {
565 public:
566 virtual ~Concept() noexcept(false)
567 {
568 }
569
570 Concept(const Concept&) = delete;
571 Concept& operator=(const Concept&) = delete;
572 Concept(Concept&&) = delete;
573 Concept& operator=(Concept&&) = delete;
574
575 [[nodiscard]]
576 virtual bool is_satisfied() const = 0;
577
578 [[nodiscard]]
579 virtual const std::source_location& from() const noexcept = 0;
580
581 protected:
582 Concept() = default;
583 };
584
585 template <typename Signature>
586 class Model final
587 : public Concept
588 {
589 public:
590 using StorageT = ExpectationCollection<Signature>;
591 using ExpectationT = Expectation<Signature>;
592
593 ~Model() noexcept(false) override
594 {
595 m_Storage->remove(m_Expectation);
596 }
597
598 [[nodiscard]]
599 explicit Model(
600 std::shared_ptr<StorageT>&& storage,
601 std::shared_ptr<ExpectationT>&& expectation) noexcept
602 : m_Storage{std::move(storage)},
603 m_Expectation{std::move(expectation)}
604 {
605 assert(m_Storage && "Storage is nullptr.");
606 assert(m_Expectation && "Expectation is nullptr.");
607
608 m_Storage->push(m_Expectation);
609 }
610
611 [[nodiscard]]
612 bool is_satisfied() const override
613 {
614 return m_Expectation->is_satisfied();
615 }
616
617 [[nodiscard]]
618 const std::source_location& from() const noexcept override
619 {
620 return m_Expectation->from();
621 }
622
623 private:
624 std::shared_ptr<StorageT> m_Storage;
625 std::shared_ptr<ExpectationT> m_Expectation;
626 };
627
628 public:
633 ~ScopedExpectation() noexcept(false) // NOLINT(modernize-use-equals-default)
634 {
635 // we must call the dtor manually here, because std::unique_ptr's dtor mustn't throw.
636 delete m_Inner.release(); // NOLINT(*-uniqueptr-delete-release)
637 }
638
645 template <typename Signature>
646 [[nodiscard]]
648 std::shared_ptr<ExpectationCollection<Signature>> collection,
649 std::shared_ptr<typename ExpectationCollection<Signature>::ExpectationT> expectation) noexcept
650 : m_Inner{
651 std::make_unique<Model<Signature>>(
652 std::move(collection),
653 std::move(expectation))}
654 {
655 }
656
663 template <typename T>
664 requires requires(const std::source_location& loc) {
665 { std::declval<T&&>().finalize(loc) } -> std::convertible_to<ScopedExpectation>;
666 }
667 [[nodiscard]]
668 explicit(false) constexpr ScopedExpectation(T&& object, const std::source_location& loc = std::source_location::current())
669 : ScopedExpectation{std::forward<T>(object).finalize(loc)}
670 {
671 }
672
676 ScopedExpectation(const ScopedExpectation&) = delete;
677
682
686 [[nodiscard]]
688
693
698 [[nodiscard]]
699 bool is_satisfied() const
700 {
701 return m_Inner->is_satisfied();
702 }
703
708 [[nodiscard]]
709 const std::source_location& from() const noexcept
710 {
711 return m_Inner->from();
712 }
713
714 private:
715 std::unique_ptr<Concept> m_Inner;
716 };
717
721}
722
723#endif
FinalizePolicy FinalizerT
Definition Expectation.hpp:420
constexpr void consume(const CallInfoT &call) override
Informs all policies, that the given call has been accepted.
Definition Expectation.hpp:508
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:439
ControlPolicy ControlPolicyT
Definition Expectation.hpp:419
constexpr bool is_satisfied() const noexcept override
Queries all policies, whether they are satisfied.
Definition Expectation.hpp:473
typename Expectation< Signature >::ReturnT ReturnT
Definition Expectation.hpp:423
constexpr ReturnT finalize_call(const CallInfoT &call) override
Requests the given call to be finalized.
Definition Expectation.hpp:522
ExpectationReport report() const override
Creates a report of the internal state.
Definition Expectation.hpp:455
call::info_for_signature_t< Signature > CallInfoT
Definition Expectation.hpp:422
MatchReport matches(const CallInfoT &call) const override
Queries all policies, whether they accept the given call.
Definition Expectation.hpp:487
std::tuple< Policies... > PolicyListT
Definition Expectation.hpp:421
constexpr const std::source_location & from() const noexcept override
Returns the source-location, where this expectation has been created.
Definition Expectation.hpp:531
Definition ControlPolicies.hpp:156
Collects all expectations for a specific (decayed) signature.
Definition Fwd.hpp:327
ExpectationCollection(const ExpectationCollection &)=delete
Deleted copy-constructor.
~ExpectationCollection()=default
Defaulted destructor.
signature_return_type_t< Signature > ReturnT
The return type.
Definition Expectation.hpp:219
ExpectationCollection & operator=(const ExpectationCollection &)=delete
Deleted copy-assignment-operator.
ReturnT handle_call(CallInfoT call)
Handles the incoming call.
Definition Expectation.hpp:302
ExpectationCollection(ExpectationCollection &&)=default
Defaulted move-constructor.
void push(std::shared_ptr< ExpectationT > expectation)
Inserts the given expectation into the internal storage.
Definition Expectation.hpp:259
ExpectationCollection()=default
Defaulted default constructor.
call::info_for_signature_t< Signature > CallInfoT
The expected call type.
Definition Expectation.hpp:209
ExpectationCollection & operator=(ExpectationCollection &&)=default
Defaulted move-assignment-operator.
void remove(std::shared_ptr< ExpectationT > expectation)
Removes the given expectation from the internal storage.
Definition Expectation.hpp:277
Expectation< Signature > ExpectationT
The interface type of the stored expectations.
Definition Expectation.hpp:214
The base interface for expectations.
Definition Fwd.hpp:323
Expectation(Expectation &&)=delete
Deleted move-constructor.
call::info_for_signature_t< Signature > CallInfoT
The expected call type.
Definition Expectation.hpp:113
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.
virtual MatchReport matches(const CallInfoT &call) const =0
Queries all policies, whether they accept the given call.
signature_return_type_t< Signature > ReturnT
The return type.
Definition Expectation.hpp:118
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.
Expectation & operator=(Expectation &&)=delete
Deleted move-assignment-operator.
Contains the extracted info from a typed expectation.
Definition Reports.hpp:331
Information a used expectation policy.
Definition Reports.hpp:436
Contains the detailed information for match outcomes.
Definition Reports.hpp:417
bool is_satisfied() const
Queries the stored expectation, whether it's satisfied.
Definition Expectation.hpp:699
const std::source_location & loc
Definition Expectation.hpp:668
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:709
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:647
ScopedExpectation & operator=(const ScopedExpectation &)=delete
Deleted copy-assignment-operator.
ScopedExpectation & operator=(ScopedExpectation &&)=default
Defaulted move-assignment-operator.
~ScopedExpectation() noexcept(false)
Removes the owned expectation from the ExpectationCollection and checks, whether it's satisfied.
Definition Expectation.hpp:633
Determines, whether the given type satisfies the requirements of a control-policy.
Definition Expectation.hpp:394
Determines, whether the given type satisfies the requirements of an expectation-policy for the given ...
Definition Expectation.hpp:369
Determines, whether the given type satisfies the requirements of a finalize-policy for the given sign...
Definition Expectation.hpp:383
MatchResult evaluate_match_report(const MatchReport &report) noexcept
Determines, whether a match report actually denotes a full, inapplicable or no match.
Definition Reports.hpp:468
CallReport make_call_report(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:157
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:173
Definition Call.hpp:20
typename info_for_signature< Signature >::type info_for_signature_t
Definition Call.hpp:43
Definition FloatingPointMatchers.hpp:22
Definition BoostTest.hpp:20
MatchResult
Definition Fwd.hpp:332
@ inapplicable
Definition Fwd.hpp:334
@ none
Definition Fwd.hpp:333
@ full
Definition Fwd.hpp:335
void unreachable()
Invokes undefined behavior.
Definition Utility.hpp:91