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

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/Generators/Tuple.hpp
diff options
context:
space:
mode:
authorLeitwolf112026-10-02 03:27:05 +0300
committerLeitwolf112026-10-02 03:27:05 +0300
commit5d55a3226360fae1eaa5ef91908b0fa7d7d07234 (patch)
tree61e94c3bd1684b4caa256bc34e380c272b67cdb6 /src/Progmasoft/Catch3/Generators/Tuple.hpp
parentfebb5b1e244174da0985b9994f8d5ed1676950e8 (diff)
downloadcatch3-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.hpp83
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