diff options
| author | Leitwolf11 | 2026-10-02 03:27:05 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-02 03:27:05 +0300 |
| commit | 5d55a3226360fae1eaa5ef91908b0fa7d7d07234 (patch) | |
| tree | 61e94c3bd1684b4caa256bc34e380c272b67cdb6 /src/Progmasoft/Catch3/Generators/Tuple.hpp | |
| parent | febb5b1e244174da0985b9994f8d5ed1676950e8 (diff) | |
| download | catch3-feature/property-combinators.tar.gz | |
Add composite property generators, single-trial replay and stable counterexample printingfeature/property-combinators
Property tests could only generate one integer or Boolean. This adds the
composite strategies the documentation listed as missing, without changing
any Catch2 file or the existing Progmasoft API.
Generators:
- Tuple(generators...) draws components left to right and shrinks one
component at a time, which is how a property receives several arguments.
- Vector(element, minimumSize, maximumSize) and Text(alphabet,
minimumLength, maximumLength) share one ordered shrink: shortest allowed
prefix, the two halves, single removals, then single element
simplifications. A shrunk value never falls below its minimum, each step
offers at most 256 candidates, and a size limit above 1,048,576 is
rejected.
- Element(values) picks from a fixed list and shrinks toward earlier
entries.
Runner:
- PropertyOptions::ReplayTrialSeed runs exactly one trial with a reported
replay seed, including its shrinking, so a failure from a log can be
reproduced without rerunning earlier trials. The report now ends with the
line that names this option and the seed.
- Counterexamples of tuples, pairs and vectors are printed element by
element instead of relying on Catch2's optional string makers, so the
report does not depend on configuration macros or include order.
The headers still compile for clients built without language exceptions.
Thirteen new test cases cover bounds, determinism, shrink order, candidate
limits, minimized counterexamples, replay and invalid configurations, and
every build description (Bazel, CMake, Meson, Xmake, Premake) lists the new
headers and test file.
Diffstat (limited to 'src/Progmasoft/Catch3/Generators/Tuple.hpp')
| -rw-r--r-- | src/Progmasoft/Catch3/Generators/Tuple.hpp | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/src/Progmasoft/Catch3/Generators/Tuple.hpp b/src/Progmasoft/Catch3/Generators/Tuple.hpp new file mode 100644 index 00000000..df58da05 --- /dev/null +++ b/src/Progmasoft/Catch3/Generators/Tuple.hpp @@ -0,0 +1,83 @@ +// 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> + requires(sizeof...(ValueTypes) > 0) + [[nodiscard]] Generator<std::tuple<ValueTypes...>> + Tuple(Generator<ValueTypes>... generators) + { + 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 |