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

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/Detail
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/Detail
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/Detail')
-rw-r--r--src/Progmasoft/Catch3/Detail/Sequence.hpp97
1 files changed, 97 insertions, 0 deletions
diff --git a/src/Progmasoft/Catch3/Detail/Sequence.hpp b/src/Progmasoft/Catch3/Detail/Sequence.hpp
new file mode 100644
index 00000000..b1446a79
--- /dev/null
+++ b/src/Progmasoft/Catch3/Detail/Sequence.hpp
@@ -0,0 +1,97 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+#pragma once
+
+#include <cstddef>
+#include <iterator>
+#include <utility>
+#include <vector>
+
+namespace Progmasoft::Catch3::Detail
+{
+
+ /// Upper bound on the candidates one sequence value offers per shrink
+ /// step. The property runner re-shrinks after every accepted candidate,
+ /// so a bounded list still reaches small counterexamples while keeping
+ /// each step linear in this constant instead of in the sequence length.
+ inline constexpr std::size_t kMaximumSequenceCandidates = 256;
+
+ /// Largest sequence a built-in generator will create. A size range above
+ /// this is rejected instead of silently allocating gigabytes in a test.
+ inline constexpr std::size_t kMaximumSequenceLength = std::size_t{ 1 }
+ << 20U;
+
+ /**
+ * @brief Ordered shrink candidates shared by Vector and Text.
+ *
+ * Candidates are produced in a fixed order, simplest first:
+ *
+ * 1. the shortest allowed prefix,
+ * 2. the first half and then the second half,
+ * 3. the value with one element removed, position by position,
+ * 4. the value with one element replaced by each of that element's own
+ * shrink candidates, position by position.
+ *
+ * No candidate is shorter than @p minimumSize, every candidate differs
+ * from @p value in length or in exactly one element, and the list is cut
+ * at kMaximumSequenceCandidates. The same candidate may appear twice,
+ * for example when removing the last element equals the shortest prefix;
+ * the runner simply tests it again.
+ */
+ template<typename Sequence, typename ShrinkElement>
+ [[nodiscard]] std::vector<Sequence>
+ ShrinkSequence(const Sequence &value,
+ std::size_t minimumSize,
+ ShrinkElement &&shrinkElement)
+ {
+ using Element = typename Sequence::value_type;
+ using Difference = typename std::iterator_traits<
+ typename Sequence::const_iterator>::difference_type;
+
+ std::vector<Sequence> candidates;
+ const std::size_t size = value.size();
+ const auto full = [&candidates] {
+ return candidates.size() >= kMaximumSequenceCandidates;
+ };
+ const auto at = [&value](std::size_t index) {
+ return value.begin() + static_cast<Difference>(index);
+ };
+
+ if (size > minimumSize)
+ {
+ candidates.push_back(Sequence(at(0), at(minimumSize)));
+
+ const std::size_t kept = size - size / 2;
+ if (kept > minimumSize && kept < size)
+ {
+ candidates.push_back(Sequence(at(0), at(kept)));
+ candidates.push_back(Sequence(at(size - kept), at(size)));
+ }
+
+ for (std::size_t index = 0; index < size && !full(); ++index)
+ {
+ Sequence shorter(at(0), at(index));
+ shorter.insert(shorter.end(), at(index + 1), at(size));
+ candidates.push_back(std::move(shorter));
+ }
+ }
+
+ for (std::size_t index = 0; index < size && !full(); ++index)
+ {
+ const Element current = static_cast<Element>(*at(index));
+ for (const auto &simpler : shrinkElement(current))
+ {
+ if (full())
+ {
+ break;
+ }
+ Sequence replaced = value;
+ *(replaced.begin() + static_cast<Difference>(index))
+ = static_cast<Element>(simpler);
+ candidates.push_back(std::move(replaced));
+ }
+ }
+ return candidates;
+ }
+
+} // namespace Progmasoft::Catch3::Detail