• 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

98.59
/include/mimic++/matchers/GeneralMatchers.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_MATCHERS_GENERAL_MATCHERS_HPP
7
#define MIMICPP_MATCHERS_GENERAL_MATCHERS_HPP
8

9
#pragma once
10

11
#include "mimic++/Fwd.hpp"
12
#include "mimic++/config/Config.hpp"
13
#include "mimic++/matchers/Common.hpp"
14
#include "mimic++/printing/Format.hpp"
15
#include "mimic++/printing/Fwd.hpp"
16
#include "mimic++/printing/StatePrinter.hpp"
17
#include "mimic++/utilities/Concepts.hpp"
18

19
#ifndef MIMICPP_DETAIL_IS_MODULE
20
    #include <functional>
21
    #include <tuple>
22
    #include <type_traits>
23
    #include <utility>
24
#endif
25

26
namespace mimicpp::detail
27
{
28
    template <typename Arg, typename MatchesProjection = std::identity, typename DescribeProjection = printing::PrintFn>
29
    struct arg_storage
30
    {
31
        using matches_reference = std::invoke_result_t<MatchesProjection, const Arg&>;
32
        using describe_reference = std::invoke_result_t<DescribeProjection, const Arg&>;
33

34
        Arg arg;
35

36
        decltype(auto) as_matches_arg() const noexcept(std::is_nothrow_invocable_v<MatchesProjection, const Arg&>)
37
        {
38
            return std::invoke(MatchesProjection{}, arg);
14,644✔
39
        }
40

41
        decltype(auto) as_describe_arg() const noexcept(std::is_nothrow_invocable_v<DescribeProjection, const Arg&>)
42
        {
43
            return std::invoke(DescribeProjection{}, arg);
8,173✔
44
        }
45
    };
46

47
    template <typename T>
48
    struct to_arg_storage
49
    {
50
        using type = arg_storage<T>;
51
    };
52

53
    template <typename Arg, typename MatchesProjection, typename DescribeProjection>
54
    struct to_arg_storage<arg_storage<Arg, MatchesProjection, DescribeProjection>>
55
    {
56
        using type = arg_storage<Arg, MatchesProjection, DescribeProjection>;
57
    };
58

59
    template <typename T>
60
    using to_arg_storage_t = typename to_arg_storage<T>::type;
61
}
62

63
MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp
64
{
65
    /**
66
     * \brief Generic matcher and the basic building block of most of the built-in matchers.
67
     * \tparam Predicate The predicate type.
68
     * \tparam AdditionalArgs Addition argument types.
69
     * \ingroup MATCHERS
70
     */
71
    template <typename Predicate, typename... AdditionalArgs>
72
        requires std::is_move_constructible_v<Predicate>
73
              && (... && std::is_move_constructible_v<AdditionalArgs>)
74
    class PredicateMatcher
75
    {
76
    private:
77
        using storage_t = std::tuple<detail::to_arg_storage_t<AdditionalArgs>...>;
78
        template <typename T>
79
        using matches_reference_t = typename detail::to_arg_storage_t<T>::matches_reference;
80

81
    public:
82
        [[nodiscard]]
83
        explicit constexpr PredicateMatcher(
84
            Predicate predicate,
85
            StringViewT fmt,
86
            StringViewT invertedFmt,
87
            std::tuple<AdditionalArgs...> additionalArgs = {})
88
            noexcept(
89
                std::is_nothrow_move_constructible_v<Predicate>
90
                && (... && std::is_nothrow_move_constructible_v<AdditionalArgs>))
91
            : m_Predicate{std::move(predicate)},
9,082✔
92
              m_FormatString{std::move(fmt)},
9,082✔
93
              m_InvertedFormatString{std::move(invertedFmt)},
9,082✔
94
              m_AdditionalArgs{std::move(additionalArgs)}
18,094✔
95
        {
96
        }
9,082✔
97

98
        template <typename First, typename... Others>
99
            requires std::predicate<
100
                const Predicate&,
101
                First&,
102
                Others&...,
103
                matches_reference_t<AdditionalArgs>...>
104
        [[nodiscard]]
105
        constexpr bool matches(
106
            First& first,
107
            Others&... others) const
108
            noexcept(
109
                std::is_nothrow_invocable_v<
110
                    const Predicate&,
111
                    First&,
112
                    Others&...,
113
                    matches_reference_t<AdditionalArgs>...>)
114
        {
115
            return std::apply(
29,166✔
116
                [&, this](auto&... additionalArgs) {
14,536✔
117
                    return std::invoke(
14,583✔
118
                        m_Predicate,
119
                        first,
120
                        others...,
121
                        additionalArgs.as_matches_arg()...);
26,169✔
122
                },
123
                m_AdditionalArgs);
26,450✔
124
        }
125

126
        [[nodiscard]]
127
        constexpr StringT describe() const
128
        {
129
            return std::apply(
130
                [&, this](auto&... additionalArgs) {
8,060✔
131
                    return format::vformat(
132
                        m_FormatString,
133
                        format::make_format_args(
8,091✔
134
                            std::invoke(
8,156✔
135
                                // std::make_format_args requires lvalue-refs, so let's transform rvalue-refs to const lvalue-refs
136
                                [](auto&& val) noexcept -> const auto& { return val; },
×
137
                                additionalArgs.as_describe_arg())...));
16,346✔
138
                },
139
                m_AdditionalArgs);
16,182✔
140
        }
141

142
        [[nodiscard]]
143
        constexpr auto operator!() const&
144
            requires std::is_copy_constructible_v<Predicate>
145
                  && std::is_copy_constructible_v<storage_t>
146
        {
147
            return make_inverted(
12✔
148
                m_Predicate,
12✔
149
                m_InvertedFormatString,
150
                m_FormatString,
151
                m_AdditionalArgs);
12✔
152
        }
153

154
        [[nodiscard]]
155
        constexpr auto operator!() &&
156
        {
157
            return make_inverted(
1,231✔
158
                std::move(m_Predicate),
2,452✔
159
                std::move(m_InvertedFormatString),
1,231✔
160
                std::move(m_FormatString),
2,452✔
161
                std::move(m_AdditionalArgs));
3,693✔
162
        }
163

164
    private:
165
        [[no_unique_address]] Predicate m_Predicate;
166
        StringViewT m_FormatString;
167
        StringViewT m_InvertedFormatString;
168
        storage_t m_AdditionalArgs;
169

170
        template <typename Fn>
171
        [[nodiscard]]
172
        static constexpr auto make_inverted(
173
            Fn&& fn,
174
            StringViewT fmt,
175
            StringViewT invertedFmt,
176
            storage_t tuple)
177
        {
178
            using NotFnT = decltype(std::not_fn(std::forward<Fn>(fn)));
179
            return PredicateMatcher<NotFnT, detail::to_arg_storage_t<AdditionalArgs>...>{
180
                std::not_fn(std::forward<Fn>(fn)),
1,243✔
181
                std::move(fmt),
1,243✔
182
                std::move(invertedFmt),
1,243✔
183
                std::move(tuple)};
3,729✔
184
        }
185
    };
186

187
    /**
188
     * \brief Matcher, which never fails.
189
     * \ingroup MATCHERS
190
     * \snippet Requirements.cpp matcher wildcard
191
     */
192
    class WildcardMatcher
193
    {
194
    public:
195
        [[nodiscard]]
196
        static constexpr bool matches([[maybe_unused]] auto&& target) noexcept
197
        {
198
            return true;
23✔
199
        }
200

201
        [[nodiscard]]
202
        static constexpr std::nullopt_t describe() noexcept
203
        {
204
            return std::nullopt;
16✔
205
        }
206
    };
207

208
    /**
209
     * \brief Matcher, which can be used to disambiguate between similar overloads.
210
     * \ingroup MATCHERS
211
     * \snippet Requirements.cpp matcher type
212
     */
213
    template <typename T>
214
    class TypeMatcher
215
    {
216
    public:
217
        template <typename U>
218
        using is_accepting = std::is_same<T, U>;
219

220
        [[nodiscard]]
221
        static constexpr bool matches([[maybe_unused]] auto&& target) noexcept
222
        {
223
            return true;
3✔
224
        }
225

226
        [[nodiscard]]
227
        static constexpr std::nullopt_t describe() noexcept
228
        {
229
            return std::nullopt;
3✔
230
        }
231
    };
232
}
233

234
MIMICPP_DETAIL_MODULE_EXPORT namespace mimicpp::matches
235
{
236
    /**
237
     * \defgroup MATCHERS matchers
238
     * \brief Matchers check various argument properties.
239
     * \details Matchers can be used to check various argument properties and are highly customizable. In general,
240
     * they simply compare their arguments with a pre-defined predicate, but also provide a meaningful description.
241
     *
242
     * \attention Matchers receive their arguments as possibly non-const, which is due to workaround some restrictions
243
     * on const qualified views. Either way, matchers should never modify any of their arguments.
244
     *
245
     * ### Matching arguments
246
     * In general matchers can be applied via the ``expect::arg<n>`` factory, but they can also be directly used
247
     * at the expect statement.
248
     * \snippet Requirements.cpp expect::arg
249
     * \snippet Requirements.cpp expect arg matcher
250
     *
251
     * \details For equality testing, there exists an even shorter syntax.
252
     * \snippet Requirements.cpp expect arg equal short
253
     *
254
     * \details Most of the built-in matchers support the inversion operator (``operator !``), which then tests for the opposite
255
     * condition.
256
     * \snippet Requirements.cpp matcher inverted
257
     *
258
     * ### Custom Matcher
259
     * Matchers are highly customizable. In fact, any type which satisfies ``matcher_for`` concept can be used.
260
     * There exists no base or interface type, but the ``PredicateMatcher`` servers as a convenient generic type, which
261
     * simply contains a predicate, a format string and optional additional arguments.
262
     *
263
     * A very straight-forward custom matcher may look like this:
264
     * \snippet CustomMatcher.cpp matcher custom contains definition
265
     * \snippet CustomMatcher.cpp matcher custom contains usage
266
     *
267
     * In fact, the `PredicateMatcher` is very flexible and can most likely tailored to your needs.
268
     * For example, you can store any additional data.
269
     * In this case the internal formatter requires the raw-pattern string, but the actual predicate needs a `std::regex`.
270
     * \snippet CustomMatcher.cpp matcher custom regex definition
271
     * \snippet CustomMatcher.cpp matcher custom regex usage
272
     *
273
     * Variadic matchers are also directly supported.
274
     * In this case, the matcher requires two inputs and checks whether the sum of both matches the specified value.
275
     * \snippet CustomMatcher.cpp matcher custom variadic definition
276
     * \snippet CustomMatcher.cpp matcher custom variadic usage
277
     *
278
     * When there are very special needs, users can also just define their own matcher type without any base-class.
279
     * \snippet CustomMatcher.cpp matcher custom standalone definition
280
     * \snippet CustomMatcher.cpp matcher custom standalone usage
281
     *
282
     *\{
283
     */
284

285
    /**
286
     * \brief The wildcard matcher, always matching.
287
     * \snippet Requirements.cpp matcher wildcard
288
     */
289
    [[maybe_unused]] inline constexpr WildcardMatcher _{};
290

291
    /**
292
     * \brief Matcher, which can be used as a last resort to disambiguate similar overloads.
293
     * \tparam T The exact argument type.
294
     * \snippet Requirements.cpp matcher type
295
     */
296
    template <typename T>
297
    [[maybe_unused]] inline constexpr TypeMatcher<T> type{};
298

299
    /**
300
     * \brief Tests, whether the target compares equal to the expected value.
301
     * \tparam T Expected type.
302
     * \param value Expected value.
303
     */
304
    template <typename T>
305
    [[nodiscard]]
306
    constexpr auto eq(T&& value)
307
    {
308
        return PredicateMatcher{
309
            std::equal_to{},
310
            "== {}",
1,827✔
311
            "!= {}",
1,827✔
312
            std::make_tuple(std::forward<T>(value))};
1,827✔
313
    }
314

315
    /**
316
     * \brief Tests, whether the target compares not equal to the expected value.
317
     * \tparam T Expected type.
318
     * \param value Expected value.
319
     */
320
    template <typename T>
321
    [[nodiscard]]
322
    constexpr auto ne(T&& value)
323
    {
324
        return PredicateMatcher{
325
            std::not_equal_to{},
326
            "!= {}",
2✔
327
            "== {}",
2✔
328
            std::make_tuple(std::forward<T>(value))};
2✔
329
    }
330

331
    /**
332
     * \brief Tests, whether the target is less than the expected value.
333
     * \tparam T Expected type.
334
     * \param value Expected value.
335
     */
336
    template <typename T>
337
    [[nodiscard]]
338
    constexpr auto lt(T&& value)
339
    {
340
        return PredicateMatcher{
341
            std::less{},
342
            "< {}",
1✔
343
            ">= {}",
1✔
344
            std::make_tuple(std::forward<T>(value))};
1✔
345
    }
346

347
    /**
348
     * \brief Tests, whether the target is less than or equal to the expected value.
349
     * \tparam T Expected type.
350
     * \param value Expected value.
351
     */
352
    template <typename T>
353
    [[nodiscard]]
354
    constexpr auto le(T&& value)
355
    {
356
        return PredicateMatcher{
357
            std::less_equal{},
358
            "<= {}",
3✔
359
            "> {}",
3✔
360
            std::make_tuple(std::forward<T>(value))};
3✔
361
    }
362

363
    /**
364
     * \brief Tests, whether the target is greater than the expected value.
365
     * \tparam T Expected type.
366
     * \param value Expected value.
367
     */
368
    template <typename T>
369
    [[nodiscard]]
370
    constexpr auto gt(T&& value)
371
    {
372
        return PredicateMatcher{
373
            std::greater{},
374
            "> {}",
6✔
375
            "<= {}",
6✔
376
            std::make_tuple(std::forward<T>(value))};
6✔
377
    }
378

379
    /**
380
     * \brief Tests, whether the target is greater than or equal to the expected value.
381
     * \tparam T Expected type.
382
     * \param value Expected value.
383
     */
384
    template <typename T>
385
    [[nodiscard]]
386
    constexpr auto ge(T&& value)
387
    {
388
        return PredicateMatcher{
389
            std::greater_equal{},
390
            ">= {}",
4✔
391
            "< {}",
4✔
392
            std::make_tuple(std::forward<T>(value))};
4✔
393
    }
394

395
    /**
396
     * \brief Tests, whether the target fulfills the given predicate.
397
     * \tparam UnaryPredicate Predicate type.
398
     * \param predicate The predicate to test.
399
     * \param description The formatting string.
400
     * \param invertedDescription The formatting string for the inversion.
401
     * \snippet Requirements.cpp matcher predicate
402
     */
403
    template <typename UnaryPredicate>
404
    [[nodiscard]]
405
    constexpr auto predicate(
406
        UnaryPredicate&& predicate,
407
        StringViewT description = "passes predicate",
408
        StringViewT invertedDescription = "fails predicate")
409
    {
410
        return PredicateMatcher{
411
            std::forward<UnaryPredicate>(predicate),
29✔
412
            std::move(description),
29✔
413
            std::move(invertedDescription),
29✔
414
        };
83✔
415
    }
416

417
    /**
418
     * \brief Tests, whether the target is the expected instance.
419
     * \tparam T Instance type.
420
     * \param instance The instance to be compared to.
421
     * \snippet Requirements.cpp matcher instance
422
     */
423
    template <util::satisfies<std::is_lvalue_reference> T>
424
    [[nodiscard]]
425
    constexpr auto instance(T&& instance) // NOLINT(cppcoreguidelines-missing-std-forward)
426
    {
427
        return PredicateMatcher{
428
            []<typename Other>(Other const& target, auto const* instancePtr) noexcept
429
                requires std::is_convertible_v<std::remove_cvref_t<T> const volatile*, Other const volatile*>
430
            {
431
                return std::addressof(target) == instancePtr;
97✔
432
            },
433
            "is instance at {}",
99✔
434
            "is not instance at {}",
99✔
435
            std::make_tuple(std::addressof(instance))};
99✔
436
    }
437

438
    /**
439
     * \}
440
     */
441
}
442

443
#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