• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DNKpp / mimicpp / 18388726968

09 Oct 2025 08:46PM UTC coverage: 92.629% (-5.5%) from 98.112%
18388726968

Pull #138

github

web-flow
Merge b67d4c028 into 798c20ab0
Pull Request #138: CI: Use latest gcovr

1797 of 2079 branches covered (86.44%)

Branch coverage included in aggregate %.

2413 of 2466 relevant lines covered (97.85%)

7268.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.82
/include/mimic++/Expectation.hpp
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++/Fwd.hpp"
13
#include "mimic++/Sequence.hpp"
14
#include "mimic++/TypeTraits.hpp"
15
#include "mimic++/config/Config.hpp"
16
#include "mimic++/config/Settings.hpp"
17
#include "mimic++/reporting/CallReport.hpp"
18
#include "mimic++/reporting/ExpectationReport.hpp"
19
#include "mimic++/reporting/GlobalReporter.hpp"
20
#include "mimic++/reporting/TargetReport.hpp"
21
#include "mimic++/utilities/Concepts.hpp"
22
#include "mimic++/utilities/SourceLocation.hpp"
23

24
#ifndef MIMICPP_DETAIL_IS_MODULE
25
    #include <algorithm>
26
    #include <concepts>
27
    #include <functional>
28
    #include <memory>
29
    #include <mutex>
30
    #include <optional>
31
    #include <ranges>
32
    #include <tuple>
33
    #include <utility>
34
    #include <vector>
35
#endif
36

37
namespace mimicpp::detail
38
{
39
    template <typename Return, typename... Params, typename Signature>
40
    [[nodiscard]]
41
    std::optional<reporting::RequirementOutcomes> determine_requirement_outcomes(
42
        reporting::TargetReport const& target,
43
        call::Info<Return, Params...> const& call,
44
        Expectation<Signature> const& expectation) noexcept
45
    {
46
        try
47
        {
48
            return expectation.matches(call);
32,879✔
49
        }
50
        catch (...)
3✔
51
        {
52
            reporting::detail::report_unhandled_exception(
3✔
53
                reporting::make_call_report(
6✔
54
                    target,
55
                    call,
56
                    util::stacktrace::current(3u + call.baseStacktraceSkip)),
6✔
57
                expectation.report(),
6✔
58
                std::current_exception());
6✔
59
        }
60

61
        return std::nullopt;
3✔
62
    }
63

64
    [[nodiscard]]
65
    std::vector<reporting::ExpectationReport> gather_expectation_reports(auto&& expectationPtrs)
66
    {
67
        auto view = std::views::transform(expectationPtrs, [](auto const& exp) { return exp->report(); });
20,913✔
68
        return std::vector<reporting::ExpectationReport>{
69
            view.begin(),
20,914✔
70
            view.end()};
62,742✔
71
    }
72

73
    template <typename Signature>
74
    [[nodiscard]]
75
    std::vector<reporting::NoMatchReport> make_no_match_reports(
76
        std::vector<std::tuple<Expectation<Signature>*, reporting::RequirementOutcomes>>&& outcomes)
77
    {
78
        std::vector<reporting::NoMatchReport> reports{};
19✔
79
        reports.reserve(outcomes.size());
19✔
80
        for (auto const& [expectationPtr, outcome] : outcomes)
48✔
81
        {
82
            reports.emplace_back(
5✔
83
                expectationPtr->report(),
5✔
84
                std::move(outcome));
5✔
85
        }
86

87
        return reports;
19✔
88
    }
89

90
    [[nodiscard]]
91
    constexpr auto find_best_match(std::span<reporting::ExpectationReport const> const matches)
92
    {
93
        constexpr auto ratings = [](auto const& el) noexcept -> const auto& {
94
            return std::get<reporting::state_applicable>(el.controlReport)
18✔
95
                .sequenceRatings;
18✔
96
        };
97

98
        auto best = std::ranges::begin(matches);
20,911✔
99
        for (auto iter = best + 1;
20,911✔
100
             iter != std::ranges::end(matches);
41,840✔
101
             ++iter)
102
        {
103
            if (!sequence::detail::has_better_rating(
9✔
104
                    ratings(*best),
9✔
105
                    ratings(*iter)))
9✔
106
            {
107
                best = iter;
3✔
108
            }
109
        }
110

111
        return std::ranges::distance(std::ranges::begin(matches), best);
20,911✔
112
    }
113
}
114

115
MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp
116
{
117
    /**
118
     * \defgroup EXPECTATION expectation
119
     * \brief Contains everything related to managing expectations.
120
     * \details Expectations are one of the two core aspects of ``mimic++``. They define how a ``Mock`` is expected to be called.
121
     * Users should always store expectations in the ``ScopedExpectation`` RAII-object, which checks whether the expectation is satisfied
122
     * during destruction. If not, an error is forwarded to the installed reporter.
123
     * To simplify that process, the macro \ref MIMICPP_SCOPED_EXPECTATION (and the shorthand \ref SCOPED_EXP) are provided.
124
     *
125
     * Once an expectation has been fully exhausted, it becomes inactive and can't be matched any further.
126
     * If at any time multiple active expectations would be valid matches for an incoming call, ``mimic++`` has to make a choice.
127
     * In such cases, when no ``Sequence`` has been applied, the latest created expectation will be selected. This may seem rather unintuitive at first,
128
     * but as expectations are bound to their scope, usually expectations within the current scope should be preferred over the expectations from the
129
     * outer scope.
130
     *
131
     * This is the technical explanation for the behavior, but users should not rely too much on that.
132
     * In fact, ``mimic++`` just makes the guarantee, that expectations from most inside scope will be preferred over expectations from further outside.
133
     * Users should think about expectations within the same scope as equally applicable and treat such ambiguities as undeterministic outcomes.
134
     * Nevertheless, ``mimic++`` provides a tool for such cases, which is designed to work in a deterministic manner:
135
     * \ref EXPECTATION_SEQUENCE "Sequences".
136
     *
137
     * Expectations can be created anywhere in the program, you just need an appropriate ``Mock``.
138
     * \{
139
     */
140

141
    /**
142
     * \brief The base interface for expectations.
143
     * \tparam Signature The decayed signature.
144
     */
145
    template <typename Signature>
146
        requires std::same_as<Signature, signature_decay_t<Signature>>
147
    class Expectation
148
    {
149
    public:
150
        /**
151
         * \brief The expected call type.
152
         */
153
        using CallInfoT = call::info_for_signature_t<Signature>;
154

155
        /**
156
         * \brief The return type.
157
         */
158
        using ReturnT = signature_return_type_t<Signature>;
159

160
        /**
161
         * \brief Defaulted virtual destructor.
162
         */
163
        virtual ~Expectation() = default;
164

165
        /**
166
         * \brief Defaulted default constructor.
167
         */
168
        [[nodiscard]]
169
        Expectation() = default;
170

171
        /**
172
         * \brief Deleted copy-constructor.
173
         */
174
        Expectation(const Expectation&) = delete;
175

176
        /**
177
         * \brief Deleted copy-assignment-operator.
178
         */
179
        Expectation& operator=(const Expectation&) = delete;
180

181
        /**
182
         * \brief Deleted move-constructor.
183
         */
184
        Expectation(Expectation&&) = delete;
185

186
        /**
187
         * \brief Deleted move-assignment-operator.
188
         */
189
        Expectation& operator=(Expectation&&) = delete;
190

191
        /**
192
         * \brief Creates a report of the internal state.
193
         * \return A newly generated report.
194
         */
195
        [[nodiscard]]
196
        virtual reporting::ExpectationReport report() const = 0;
197

198
        /**
199
         * \brief Queries all policies, whether they are satisfied.
200
         * \return Returns true, if all policies are satisfied.
201
         */
202
        [[nodiscard]]
203
        virtual bool is_satisfied() const noexcept = 0;
204

205
        /**
206
         * \brief Queries the control policy, whether it's in the applicable state.
207
         * \return Returns true, if expectation is applicable.
208
         */
209
        [[nodiscard]]
210
        virtual bool is_applicable() const noexcept = 0;
211

212
        /**
213
         * \brief Queries all policies, whether they accept the given call.
214
         * \param call The call to be matched.
215
         * \return Returns true, if all policies accept the call.
216
         */
217
        [[nodiscard]]
218
        virtual reporting::RequirementOutcomes matches(const CallInfoT& call) const = 0;
219

220
        /**
221
         * \brief Informs all policies, that the given call has been accepted.
222
         * \param call The call to be consumed.
223
         * \details This function is called, when a match has been made.
224
         */
225
        virtual void consume(const CallInfoT& call) = 0;
226

227
        /**
228
         * \brief Requests the given call to be finalized.
229
         * \param call The call to be finalized.
230
         * \return Returns the call result.
231
         * \details This function is called, when a match has been made and ``consume`` has been called.
232
         */
233
        [[nodiscard]]
234
        virtual constexpr ReturnT finalize_call(const CallInfoT& call) = 0;
235

236
        /**
237
         * \brief Returns the source-location, where this expectation has been created.
238
         * \return Immutable reference to the source-location.
239
         */
240
        [[nodiscard]]
241
        virtual constexpr util::SourceLocation const& from() const noexcept = 0;
242

243
        /**
244
         * \brief Returns the name of the related mock.
245
         * \return Immutable reference to the mock-name.
246
         */
247
        [[nodiscard]]
248
        virtual constexpr StringT const& mock_name() const noexcept = 0;
249
    };
250

251
    /**
252
     * \brief Collects all expectations for a specific (decayed) signature.
253
     * \tparam Signature The decayed signature.
254
     */
255
    template <typename Signature>
256
        requires std::same_as<Signature, signature_decay_t<Signature>>
257
    class ExpectationCollection
258
    {
259
    public:
260
        /**
261
         * \brief The expected call type.
262
         */
263
        using CallInfoT = call::info_for_signature_t<Signature>;
264

265
        /**
266
         * \brief The interface type of the stored expectations.
267
         */
268
        using ExpectationT = Expectation<Signature>;
269

270
        /**
271
         * \brief The return type.
272
         */
273
        using ReturnT = signature_return_type_t<Signature>;
274

275
        /**
276
         * \brief Defaulted destructor.
277
         */
278
        ~ExpectationCollection() = default;
279

280
        /**
281
         * \brief Defaulted default constructor.
282
         */
283
        [[nodiscard]]
284
        ExpectationCollection() = default;
285

286
        /**
287
         * \brief Deleted copy-constructor.
288
         */
289
        ExpectationCollection(const ExpectationCollection&) = delete;
290

291
        /**
292
         * \brief Deleted copy-assignment-operator.
293
         */
294
        ExpectationCollection& operator=(const ExpectationCollection&) = delete;
295

296
        /**
297
         * \brief Defaulted move-constructor.
298
         */
299
        [[nodiscard]]
300
        ExpectationCollection(ExpectationCollection&&) = default;
301

302
        /**
303
         * \brief Defaulted move-assignment-operator.
304
         */
305
        ExpectationCollection& operator=(ExpectationCollection&&) = default;
306

307
        /**
308
         * \brief Inserts the given expectation into the internal storage.
309
         * \param expectation The expectation to be inserted.
310
         * \attention Inserting an expectation, which is already element of any ExpectationCollection (including the current one),
311
         * is undefined behavior.
312
         */
313
        void push(std::shared_ptr<ExpectationT> expectation)
314
        {
315
            const std::scoped_lock lock{m_ExpectationsMx};
20,968✔
316

317
            MIMICPP_ASSERT(std::ranges::find(m_Expectations, expectation) == std::ranges::end(m_Expectations), "Expectation already belongs to this storage.");
318

319
            m_Expectations.emplace_back(std::move(expectation));
41,936✔
320
        }
20,968✔
321

322
        /**
323
         * \brief Removes the given expectation from the internal storage.
324
         * \param expectation The expectation to be removed.
325
         * \details This function also checks, whether the removed expectation is satisfied. If not, an
326
         * "unfulfilled expectation"- report is emitted.
327
         * \attention Removing an expectation, which is not element of the current ExpectationCollection, is undefined behavior.
328
         */
329
        void remove(std::shared_ptr<ExpectationT> expectation)
330
        {
331
            const std::scoped_lock lock{m_ExpectationsMx};
20,953✔
332

333
            auto iter = std::ranges::find(m_Expectations, expectation);
20,953✔
334
            MIMICPP_ASSERT(iter != std::ranges::end(m_Expectations), "Expectation does not belong to this storage.");
335
            m_Expectations.erase(iter);
41,906✔
336

337
            if (!expectation->is_satisfied())
20,953✔
338
            {
339
                reporting::detail::report_unfulfilled_expectation(
17✔
340
                    expectation->report());
35✔
341
            }
342
        }
20,953✔
343

344
        /**
345
         * \brief Handles the incoming call.
346
         * \param target The mock-target, which received the call.
347
         * \param call The call to be handled.
348
         * \return Returns an appropriate result from the matched expectation.
349
         * \details This function queries all stored expectations, whether they accept the call.
350
         * If multiple matches are possible, the best match is selected and a "matched"-report is emitted.
351
         * If no matches are found, "no matched"-report is emitted and the call is aborted (e.g. by throwing an exception or terminating).
352
         * If matches are possible, but all expectations are saturated, an "inapplicable match"-report is emitted.
353
         */
354
        [[nodiscard]]
355
        ReturnT handle_call(reporting::TargetReport target, CallInfoT call)
356
        {
357
            std::vector<ExpectationT*> matches{};
20,933✔
358
            std::vector<ExpectationT*> inapplicableMatches{};
20,933✔
359
            std::vector<std::tuple<ExpectationT*, reporting::RequirementOutcomes>> noMatches{};
20,933✔
360

361
            std::scoped_lock const lock{m_ExpectationsMx};
20,933✔
362
            evaluate_expectations(target, call, matches, inapplicableMatches, noMatches);
20,933✔
363

364
            std::size_t const stacktraceSkip{1u + call.baseStacktraceSkip};
20,933✔
365
            if (!std::ranges::empty(matches))
20,933✔
366
            {
367
                std::vector reports = detail::gather_expectation_reports(matches);
20,911✔
368
                MIMICPP_ASSERT(matches.size() == reports.size(), "Size mismatch.");
369
                auto const bestIndex = detail::find_best_match(reports);
20,911✔
370
                MIMICPP_ASSERT(0 <= bestIndex && bestIndex < std::ssize(reports), "Invalid index.");
371

372
                auto& expectation = *matches[bestIndex];
20,911✔
373

374
                if (settings::report_success())
20,911✔
375
                {
376
                    auto& report = reports[bestIndex];
88✔
377

378
                    // Todo: Avoid the call copy
379
                    // Maybe we can prevent the copy here, but we should keep the instruction order as-is, because
380
                    // in cases of a throwing finalizer, we might introduce bugs. At least there are some tests, which
381
                    // will fail if done wrong.
382
                    reporting::detail::report_full_match(
176✔
383
                        reporting::make_call_report(std::move(target), call, util::stacktrace::current(stacktraceSkip)),
264✔
384
                        std::move(report));
88✔
385
                }
386

387
                expectation.consume(call);
20,911✔
388
                return expectation.finalize_call(call);
41,818✔
389
            }
20,911✔
390

391
            if (!std::ranges::empty(inapplicableMatches))
22✔
392
            {
393
                reporting::detail::report_inapplicable_matches(
3✔
394
                    reporting::make_call_report(std::move(target), std::move(call), util::stacktrace::current(stacktraceSkip)),
21✔
395
                    detail::gather_expectation_reports(inapplicableMatches));
9✔
396
            }
397

398
            reporting::detail::report_no_matches(
19✔
399
                reporting::make_call_report(std::move(target), std::move(call), util::stacktrace::current(stacktraceSkip)),
133✔
400
                detail::make_no_match_reports(std::move(noMatches)));
57✔
401
        }
21,011✔
402

403
    private:
404
        std::vector<std::shared_ptr<ExpectationT>> m_Expectations{};
405
        std::mutex m_ExpectationsMx{};
406

407
        void evaluate_expectations(
408
            reporting::TargetReport const& target,
409
            CallInfoT const& call,
410
            std::vector<ExpectationT*>& matches,
411
            std::vector<ExpectationT*>& inapplicableMatches,
412
            std::vector<std::tuple<ExpectationT*, reporting::RequirementOutcomes>>& noMatches)
413
        {
414
            for (auto const& exp : std::views::reverse(m_Expectations))
53,812✔
415
            {
416
                if (std::optional outcomes = detail::determine_requirement_outcomes(target, call, *exp))
65,755✔
417
                {
418
                    if (std::ranges::any_of(outcomes->outcomes, [](auto const& el) { return el == false; }))
32,876✔
419
                    {
420
                        noMatches.emplace_back(exp.get(), *std::move(outcomes));
5,722✔
421
                    }
422
                    else if (!exp->is_applicable())
27,154✔
423
                    {
424
                        inapplicableMatches.emplace_back(exp.get());
6,234✔
425
                    }
426
                    else
427
                    {
428
                        matches.emplace_back(exp.get());
20,920✔
429
                    }
430
                }
431
            }
432
        }
20,933✔
433
    };
434

435
    /**
436
     * \brief Determines, whether the given type satisfies the requirements of an expectation-policy for the given signature.
437
     */
438
    template <typename T, typename Signature>
439
    concept expectation_policy_for = std::is_move_constructible_v<T>
440
                                  && std::is_destructible_v<T>
441
                                  && std::same_as<T, std::remove_cvref_t<T>>
442
                                  && requires(T& policy, const call::info_for_signature_t<Signature>& info) {
443
                                         { std::as_const(policy).is_satisfied() } noexcept -> util::boolean_testable;
444
                                         { std::as_const(policy).matches(info) } -> util::boolean_testable;
445
                                         { std::as_const(policy).describe() } -> util::explicitly_convertible_to<std::optional<StringT>>;
446
                                         { policy.consume(info) };
447
                                     };
448

449
    /**
450
     * \brief Determines, whether the given type satisfies the requirements of a finalize-policy for the given signature.
451
     */
452
    template <typename T, typename Signature>
453
    concept finalize_policy_for = std::is_move_constructible_v<T>
454
                               && std::is_destructible_v<T>
455
                               && std::same_as<T, std::remove_cvref_t<T>>
456
                               && requires(T& policy, const call::info_for_signature_t<Signature>& info) {
457
                                      { policy.finalize_call(info) } -> std::convertible_to<signature_return_type_t<Signature>>;
458
                                  };
459

460
    /**
461
     * \brief Determines, whether the given type satisfies the requirements of a control-policy.
462
     */
463
    template <typename T>
464
    concept control_policy = std::is_move_constructible_v<T>
465
                          && std::is_destructible_v<T>
466
                          && std::same_as<T, std::remove_cvref_t<T>>
467
                          && requires(T& policy) {
468
                                 { std::as_const(policy).is_satisfied() } noexcept -> util::boolean_testable;
469
                                 { std::as_const(policy).state() } -> std::convertible_to<reporting::control_state_t>;
470
                                 policy.consume();
471
                             };
472

473
    /**
474
     * \brief The actual expectation template.
475
     * \tparam Signature The decayed signature.
476
     * \tparam ControlPolicy The applied control-policy.
477
     * \tparam FinalizePolicy The applied finalize-policy.
478
     * \tparam Policies All applied expectation-policies.
479
     */
480
    template <
481
        typename Signature,
482
        control_policy ControlPolicy,
483
        finalize_policy_for<Signature> FinalizePolicy,
484
        expectation_policy_for<Signature>... Policies>
485
    class BasicExpectation final
486
        : public Expectation<Signature>
487
    {
488
    public:
489
        using ControlPolicyT = ControlPolicy;
490
        using FinalizerT = FinalizePolicy;
491
        using PolicyListT = std::tuple<Policies...>;
492
        using CallInfoT = call::info_for_signature_t<Signature>;
493
        using ReturnT = typename Expectation<Signature>::ReturnT;
494

495
        /**
496
         * \brief Constructs the expectation with the given arguments.
497
         * \tparam ControlPolicyArg The control-policy constructor argument types.
498
         * \tparam FinalizerArg The finalize-policy constructor argument types.
499
         * \tparam PolicyArgs The expectation-policies constructor argument types.
500
         * \param from The source-location, where this expectation was created.
501
         * \param target Information about the target-mock.
502
         * \param controlArg The control-policy constructor argument.
503
         * \param finalizerArg The finalize-policy constructor argument.
504
         * \param args The expectation-policies constructor arguments.
505
         */
506
        template <typename ControlPolicyArg, typename FinalizerArg, typename... PolicyArgs>
507
            requires std::constructible_from<ControlPolicyT, ControlPolicyArg>
508
                      && std::constructible_from<FinalizerT, FinalizerArg>
509
                      && std::constructible_from<PolicyListT, PolicyArgs...>
510
        constexpr explicit BasicExpectation(
511
            util::SourceLocation from,
512
            reporting::TargetReport target,
513
            ControlPolicyArg&& controlArg,
514
            FinalizerArg&& finalizerArg,
515
            PolicyArgs&&... args)
516
            noexcept(
517
                std::is_nothrow_constructible_v<ControlPolicyT, ControlPolicyArg>
518
                && std::is_nothrow_constructible_v<FinalizerT, FinalizerArg>
519
                && (std::is_nothrow_constructible_v<Policies, PolicyArgs> && ...))
520
            : m_From{std::move(from)},
20,993✔
521
              m_Target{std::move(target)},
41,986✔
522
              m_ControlPolicy{std::forward<ControlPolicyArg>(controlArg)},
41,684✔
523
              m_Policies{std::forward<PolicyArgs>(args)...},
68,164✔
524
              m_Finalizer{std::forward<FinalizerArg>(finalizerArg)}
21,023✔
525
        {
526
        }
20,993✔
527

528
        /**
529
         * \copydoc Expectation::report
530
         */
531
        [[nodiscard]]
532
        reporting::ExpectationReport report() const override
533
        {
534
            return reporting::ExpectationReport{
535
                .from = m_From,
536
                .target = m_Target,
20,950✔
537
                .controlReport = m_ControlPolicy.state(),
538
                .finalizerDescription = std::nullopt,
539
                .requirementDescriptions = std::apply(
540
                    [&](auto const&... policies) {
×
541
                        return std::vector<std::optional<StringT>>{
542
                            std::optional<StringT>{policies.describe()}...};
151,842!
543
                    },
544
                    m_Policies)};
41,900✔
545
        }
20,950✔
546

547
        /**
548
         * \copydoc Expectation::is_satisfied
549
         */
550
        [[nodiscard]]
551
        constexpr bool is_satisfied() const noexcept override
552
        {
553
            return m_ControlPolicy.is_satisfied()
21,046✔
554
                && std::apply(
42,030✔
555
                       [](const auto&... policies) noexcept {
21,045✔
556
                           return (... && policies.is_satisfied());
20,984!
557
                       },
558
                       m_Policies);
42,030✔
559
        }
560

561
        /**
562
         * \copydoc Expectation::is_applicable
563
         */
564
        [[nodiscard]]
565
        constexpr bool is_applicable() const noexcept override
566
        {
567
            return std::holds_alternative<reporting::state_applicable>(
27,158✔
568
                m_ControlPolicy.state());
54,316✔
569
        }
570

571
        /**
572
         * \copydoc Expectation::matches
573
         */
574
        [[nodiscard]]
575
        reporting::RequirementOutcomes matches(const CallInfoT& call) const override
576
        {
577
            return reporting::RequirementOutcomes{
578
                .outcomes = gather_requirement_outcomes(call)};
32,887✔
579
        }
580

581
        /**
582
         * \copydoc Expectation::consume
583
         */
584
        constexpr void consume(const CallInfoT& call) override
585
        {
586
            m_ControlPolicy.consume();
20,922✔
587
            std::apply(
20,922✔
588
                [&](auto&... policies) noexcept {
41,842✔
589
                    (..., policies.consume(call));
20,909✔
590
                },
591
                m_Policies);
20,922✔
592
        }
20,922✔
593

594
        /**
595
         * \copydoc Expectation::finalize_call
596
         */
597
        [[nodiscard]]
598
        constexpr ReturnT finalize_call(const CallInfoT& call) override
599
        {
600
            return m_Finalizer.finalize_call(call);
20,910✔
601
        }
602

603
        /**
604
         * \copydoc Expectation::from
605
         */
606
        [[nodiscard]]
607
        constexpr util::SourceLocation const& from() const noexcept override
608
        {
609
            return m_From;
15✔
610
        }
611

612
        /**
613
         * \copydoc Expectation::mock_name
614
         */
615
        [[nodiscard]]
616
        constexpr StringT const& mock_name() const noexcept override
617
        {
618
            return m_Target.name;
18✔
619
        }
620

621
    private:
622
        util::SourceLocation m_From;
623
        reporting::TargetReport m_Target;
624
        ControlPolicyT m_ControlPolicy;
625
        PolicyListT m_Policies;
626
        [[no_unique_address]] FinalizerT m_Finalizer{};
627

628
        [[nodiscard]]
629
        std::vector<bool> gather_requirement_outcomes(CallInfoT const& call) const
630
        {
631
            return std::apply(
632
                [&](auto const&... policies) {
32,884✔
633
                    return std::vector<bool>{policies.matches(call)...};
98,623✔
634
                },
635
                m_Policies);
65,772✔
636
        }
637
    };
638

639
    /**
640
     * \brief Takes the ownership of an expectation and check whether it's satisfied during destruction.
641
     * \details The owned Expectation is type-erased. This comes in handy, when users want to store ScopedExpectations
642
     * in a single container.
643
     */
644
    class ScopedExpectation
645
    {
646
    private:
647
        class Concept
648
        {
649
        public:
650
            virtual ~Concept() noexcept(false)
651
            {
20,951✔
652
            }
20,951✔
653

654
            Concept(const Concept&) = delete;
655
            Concept& operator=(const Concept&) = delete;
656
            Concept(Concept&&) = delete;
657
            Concept& operator=(Concept&&) = delete;
658

659
            [[nodiscard]]
660
            virtual bool is_satisfied() const = 0;
661
            [[nodiscard]]
662
            virtual bool is_applicable() const = 0;
663

664
            [[nodiscard]]
665
            virtual util::SourceLocation const& from() const noexcept = 0;
666
            [[nodiscard]]
667
            virtual StringT const& mock_name() const noexcept = 0;
668

669
        protected:
670
            Concept() = default;
671
        };
672

673
        template <typename Signature>
674
        class Model final
675
            : public Concept
676
        {
677
        public:
678
            using StorageT = ExpectationCollection<Signature>;
679
            using ExpectationT = Expectation<Signature>;
680

681
            ~Model() noexcept(false) override
682
            {
683
                m_Storage->remove(m_Expectation);
20,952✔
684
            }
62,855✔
685

686
            [[nodiscard]]
687
            explicit Model(
688
                std::shared_ptr<StorageT>&& storage,
689
                std::shared_ptr<ExpectationT>&& expectation) noexcept
690
                : m_Storage{std::move(storage)},
20,951✔
691
                  m_Expectation{std::move(expectation)}
62,853✔
692
            {
693
                MIMICPP_ASSERT(m_Storage, "Storage is nullptr.");
694
                MIMICPP_ASSERT(m_Expectation, "Expectation is nullptr.");
695

696
                m_Storage->push(m_Expectation);
20,951✔
697
            }
20,951✔
698

699
            [[nodiscard]]
700
            bool is_satisfied() const override
701
            {
702
                return m_Expectation->is_satisfied();
98✔
703
            }
704

705
            [[nodiscard]]
706
            bool is_applicable() const override
707
            {
708
                return m_Expectation->is_applicable();
8✔
709
            }
710

711
            [[nodiscard]]
712
            util::SourceLocation const& from() const noexcept override
713
            {
714
                return m_Expectation->from();
15✔
715
            }
716

717
            [[nodiscard]]
718
            StringT const& mock_name() const noexcept override
719
            {
720
                return m_Expectation->mock_name();
18✔
721
            }
722

723
        private:
724
            std::shared_ptr<StorageT> m_Storage;
725
            std::shared_ptr<ExpectationT> m_Expectation;
726
        };
727

728
    public:
729
        /**
730
         * \brief Removes the owned expectation from the ExpectationCollection and checks, whether it's satisfied.
731
         * \throws In cases of an unsatisfied expectation, the destructor is expected to throw or terminate otherwise.
732
         */
733
        ~ScopedExpectation() noexcept(false)
734
        {
735
            // we must call the dtor manually here, because std::unique_ptr's dtor mustn't throw.
736
            delete m_Inner.release(); // NOLINT(*-uniqueptr-delete-release)
62,214✔
737
        }
62,214✔
738

739
        /**
740
         * \brief Constructor, which generates the type-erase storage.
741
         * \tparam Signature The signature.
742
         * \param collection The expectation collection, the expectation will be attached to.
743
         * \param expectation The expectation.
744
         */
745
        template <typename Signature>
746
        [[nodiscard]]
747
        explicit ScopedExpectation(
748
            std::shared_ptr<ExpectationCollection<Signature>> collection,
749
            std::shared_ptr<typename ExpectationCollection<Signature>::ExpectationT> expectation) noexcept
750
            : m_Inner{
41,902✔
751
                  std::make_unique<Model<Signature>>(
20,951✔
752
                      std::move(collection),
20,951✔
753
                      std::move(expectation))}
20,951✔
754
        {
755
        }
20,951✔
756

757
        /**
758
         * \brief A constructor, which accepts objects, which can be finalized (e.g. ExpectationBuilder).
759
         * \tparam T The object type.
760
         * \param object The object to be finalized.
761
         * \param loc The source-location.
762
         */
763
        template <typename T>
764
            requires requires(util::SourceLocation loc) {
765
                { std::declval<T&&>().finalize(loc) } -> std::convertible_to<ScopedExpectation>;
766
            }
767
        [[nodiscard]]
768
        explicit(false) constexpr ScopedExpectation(T&& object, util::SourceLocation loc = {})
769
            : ScopedExpectation{std::forward<T>(object).finalize(std::move(loc))}
41,866✔
770
        {
771
        }
20,933✔
772

773
        /**
774
         * \brief Deleted copy-constructor.
775
         */
776
        ScopedExpectation(ScopedExpectation const&) = delete;
777

778
        /**
779
         * \brief Deleted copy-assignment-operator.
780
         */
781
        ScopedExpectation& operator=(ScopedExpectation const&) = delete;
782

783
        /**
784
         * \brief Defaulted move-constructor.
785
         */
786
        [[nodiscard]]
787
        ScopedExpectation(ScopedExpectation&&) = default;
788

789
        /**
790
         * \brief Defaulted move-assignment-operator.
791
         */
792
        ScopedExpectation& operator=(ScopedExpectation&&) = default;
793

794
        /**
795
         * \brief Queries the stored expectation, whether it's satisfied.
796
         * \return True, if satisfied.
797
         */
798
        [[nodiscard]]
799
        bool is_satisfied() const
800
        {
801
            return m_Inner->is_satisfied();
98✔
802
        }
803

804
        /**
805
         * \brief Queries the stored expectation, whether it's applicable.
806
         * \return True, if applicable.
807
         */
808
        [[nodiscard]]
809
        bool is_applicable() const
810
        {
811
            return m_Inner->is_applicable();
8✔
812
        }
813

814
        /**
815
         * \brief Queries the stored expectation for it's stored source-location.
816
         * \return The stored source-location.
817
         */
818
        [[nodiscard]]
819
        util::SourceLocation const& from() const noexcept
820
        {
821
            return m_Inner->from();
15✔
822
        }
823

824
        /**
825
         * \brief Queries the stored expectation for the name of the related mock.
826
         * \return The stored mock-name.
827
         */
828
        [[nodiscard]]
829
        StringT const& mock_name() const noexcept
830
        {
831
            return m_Inner->mock_name();
18✔
832
        }
833

834
    private:
835
        std::unique_ptr<Concept> m_Inner;
836
    };
837

838
    /**
839
     * \}
840
     */
841
}
842

843
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc