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

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/Generators/Tuple.hpp
blob: 3e88930747d2ef1ae42512af214cb12922768804 (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
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
#pragma once

#include <Progmasoft/Catch3/Generator.hpp>
#include <cstddef>
#include <tuple>
#include <utility>
#include <vector>

namespace Progmasoft::Catch3
{
    namespace Detail
    {

        template<std::size_t Index, typename... ValueTypes>
        void
        AppendComponentCandidates(
            const std::tuple<Generator<ValueTypes>...> &generators,
            const std::tuple<ValueTypes...> &value,
            std::vector<std::tuple<ValueTypes...>> &candidates)
        {
            for (const auto &simpler :
                 std::get<Index>(generators).Shrink(std::get<Index>(value)))
            {
                std::tuple<ValueTypes...> replaced = value;
                std::get<Index>(replaced) = simpler;
                candidates.push_back(std::move(replaced));
            }
        }

        template<typename... ValueTypes, std::size_t... Indices>
        [[nodiscard]] std::vector<std::tuple<ValueTypes...>>
        ShrinkTuple(const std::tuple<Generator<ValueTypes>...> &generators,
                    const std::tuple<ValueTypes...> &value,
                    std::index_sequence<Indices...>)
        {
            std::vector<std::tuple<ValueTypes...>> candidates;
            // The comma fold visits components strictly left to right.
            (AppendComponentCandidates<Indices>(generators, value, candidates),
             ...);
            return candidates;
        }

    } // namespace Detail

    /// Combines independent generators into one generator of tuples, which
    /// is how a property receives several generated arguments.
    ///
    /// Components are drawn strictly left to right from the trial's random
    /// source, so a tuple is reproducible from its replay seed. Shrinking
    /// changes one component at a time: all candidates of the first
    /// component, then all of the second, and so on. Combined with the
    /// runner's greedy restart this minimizes every component without
    /// trying the cross product of candidates.
    template<typename... ValueTypes>
    [[nodiscard]] Generator<std::tuple<ValueTypes...>>
    Tuple(Generator<ValueTypes>... generators)
    {
        static_assert(sizeof...(ValueTypes) > 0,
                      "Tuple requires at least one generator");
        using Value = std::tuple<ValueTypes...>;
        const std::tuple<Generator<ValueTypes>...> components{ std::move(
            generators)... };

        return MakeGenerator<Value>(
            [components](Random &random) {
                return std::apply(
                    [&random](const Generator<ValueTypes> &...component) {
                        // Braced initialization fixes the evaluation order;
                        // a function-call argument list would not.
                        return Value{ component.Generate(random)... };
                    },
                    components);
            },
            [components](const Value &value) {
                return Detail::ShrinkTuple(
                    components,
                    value,
                    std::index_sequence_for<ValueTypes...>{});
            });
    }

} // namespace Progmasoft::Catch3