Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/ForAll.hpp
blob: c510db28790de422a5c29ff0f6305093a7b5de67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
#pragma once

#include <Progmasoft/Catch3/Property.hpp>
#include <Progmasoft/Catch3/Strategy/Containers.hpp>
#include <Progmasoft/Catch3/Strategy/Context.hpp>
#include <Progmasoft/Catch3/Strategy/Engine.hpp>
#include <Progmasoft/Catch3/Strategy/Strategy.hpp>
#include <exception>
#include <functional>
#include <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>

namespace Progmasoft::Catch3
{

    namespace Detail
    {

        /// Runs a body on one input and turns what happened into a verdict.
        /// A body returns bool, or void and fails through
        /// CATCH3_PROPERTY_ASSERT; an exception from it is a failure.
        template<typename Body, typename Tuple>
        void
        RunBody(Body &&body, const Tuple &values, TrialOutcome &outcome)
        {
            TrialRecordScope scope(outcome.Record);
#if defined(__cpp_exceptions)
            try
            {
#endif
                using Result = decltype(std::apply(body, values));
                if constexpr (std::is_void_v<Result>)
                {
                    std::apply(body, values);
                }
                else
                {
                    if (!static_cast<bool>(std::apply(body, values)))
                    {
                        RecordPropertyFailureMessage(
                            "property returned false");
                    }
                }
#if defined(__cpp_exceptions)
            }
            catch (const std::exception &exception)
            {
                RecordPropertyFailureMessage(
                    std::string("property threw std::exception: ")
                    + exception.what());
            }
            catch (...)
            {
                RecordPropertyFailureMessage(
                    "property threw a non-standard exception");
            }
#endif
            if (outcome.Record.Failure.has_value())
            {
                outcome.Verdict = TrialVerdict::kFailed;
                outcome.Reason = *outcome.Record.Failure;
            }
            else if (outcome.Record.Discarded)
            {
                outcome.Verdict = TrialVerdict::kDiscarded;
            }
        }

        /// Draws a value, catching what a strategy throws.
        template<typename ValueType>
        [[nodiscard]] std::optional<ValueType>
        DrawGuarded(const Strategy<ValueType> &strategy,
                    ChoiceSource &source,
                    TrialOutcome &outcome)
        {
#if defined(__cpp_exceptions)
            try
            {
#endif
                auto value = strategy.Draw(source);
                if (!value.has_value())
                {
                    outcome.Verdict = TrialVerdict::kInvalid;
                }
                return value;
#if defined(__cpp_exceptions)
            }
            catch (const std::exception &exception)
            {
                outcome.Verdict = TrialVerdict::kErrored;
                outcome.Reason = std::string("strategy threw std::exception: ")
                                 + exception.what();
            }
            catch (...)
            {
                outcome.Verdict = TrialVerdict::kErrored;
                outcome.Reason = "strategy threw a non-standard exception";
            }
            return std::nullopt;
#endif
        }

        template<typename... ValueTypes>
        [[nodiscard]] std::string
        DescribeArguments(const std::tuple<ValueTypes...> &values)
        {
            if constexpr (sizeof...(ValueTypes) == 1)
            {
                return DescribeValue(std::get<0>(values));
            }
            else
            {
                return DescribeValue(values);
            }
        }

        template<typename Body, typename... ValueTypes>
        [[nodiscard]] PropertyResult
        ForAllWith(const PropertyOptions &options,
                   Body body,
                   Strategy<ValueTypes>... strategies)
        {
            static_assert(sizeof...(ValueTypes) > 0,
                          "ForAll needs at least one strategy");
            const auto input = Gen::Tuples(std::move(strategies)...);
            const TrialRunner runner =
                [&](ChoiceSource &source, bool describe) -> TrialOutcome {
                TrialOutcome outcome;
                auto values = DrawGuarded(input, source, outcome);
                if (!values.has_value())
                {
                    return outcome;
                }
                if (describe)
                {
                    outcome.Description = DescribeArguments(*values);
                }
                RunBody(body, std::as_const(*values), outcome);
                return outcome;
            };
            return RunPropertyEngine(options, runner);
        }

        template<typename Type>
        inline constexpr bool kIsPropertyOptions
            = std::is_same_v<RemoveCvRef<Type>, PropertyOptions>;

        template<typename Arguments, std::size_t... Index>
        [[nodiscard]] PropertyResult
        ForAllWithOptions(Arguments &all, std::index_sequence<Index...>)
        {
            constexpr std::size_t kCount = std::tuple_size_v<Arguments>;
            return ForAllWith(std::get<0>(all),
                              std::get<kCount - 1U>(all),
                              std::get<Index + 1U>(all)...);
        }

        template<typename Arguments, std::size_t... Index>
        [[nodiscard]] PropertyResult
        ForAllWithDefaults(Arguments &all, std::index_sequence<Index...>)
        {
            constexpr std::size_t kCount = std::tuple_size_v<Arguments>;
            return ForAllWith(PropertyOptions{},
                              std::get<kCount - 1U>(all),
                              std::get<Index>(all)...);
        }

    } // namespace Detail

    /// Checks that a property holds for values drawn from strategies.
    ///
    ///     ForAll([options,] strategy..., body)
    ///
    /// body receives one value per strategy, as const references. It
    /// returns bool, or returns void and uses CATCH3_PROPERTY_ASSERT and
    /// CATCH3_PROPERTY_ASSUME; Note, Classify, Cover and Target may be
    /// called from it. A failure is minimized by shrinking the choices the
    /// strategies made, and the report carries a replay token for exactly
    /// the minimized input.
    template<typename... Arguments>
    [[nodiscard]] PropertyResult
    ForAll(Arguments &&...arguments)
    {
        constexpr std::size_t kCount = sizeof...(Arguments);
        static_assert(kCount >= 2, "ForAll needs strategies and a body");
        auto all = std::forward_as_tuple(std::forward<Arguments>(arguments)...);
        using First = std::tuple_element_t<0, std::tuple<Arguments...>>;
        if constexpr (Detail::kIsPropertyOptions<First>)
        {
            return Detail::ForAllWithOptions(
                all,
                std::make_index_sequence<kCount - 2U>{});
        }
        else
        {
            return Detail::ForAllWithDefaults(
                all,
                std::make_index_sequence<kCount - 1U>{});
        }
    }

} // namespace Progmasoft::Catch3