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

summaryrefslogtreecommitdiffhomepage
path: root/extras/catch_amalgamated.cpp
diff options
context:
space:
mode:
authorLeitwolf2026-10-24 19:03:40 +0300
committerGitHub2026-10-24 19:03:40 +0300
commit4f1c759cffcb14bd6511e1d7c5fbace682e24f2d (patch)
tree415fe5d48a5b48650bba3747beaf245d727c4f61 /extras/catch_amalgamated.cpp
parent83c6fc3ac13abe83fa39f7b5f8559ccaf4412233 (diff)
parent622188f9c08e4af35941c146979883ed4882410e (diff)
downloadcatch3-4f1c759cffcb14bd6511e1d7c5fbace682e24f2d.tar.gz
Merge pull request #15 from Progmasoft/feature/catch3-runner
Replace Catch2's runner with the Catch3 runner
Diffstat (limited to 'extras/catch_amalgamated.cpp')
-rw-r--r--extras/catch_amalgamated.cpp17937
1 files changed, 12042 insertions, 5895 deletions
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index 230849f1..217abac4 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -7,10 +7,14 @@
// SPDX-License-Identifier: BSL-1.0
// Catch v3.16.1
-// Generated: 2026-10-11 02:09:25.622664
+// Generated: 2026-10-24 17:43:09.597581
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
+//
+// The parts that come from src/Progmasoft, the Catch3 runner, keep
+// their own copyright and license lines (MPL-2.0 WITH
+// AdditionRef-Progmasoft-Exception-1.1). The runner needs C++20.
// ----------------------------------------------------------
#include "catch_amalgamated.hpp"
@@ -36,6 +40,2123 @@
#endif // defined(CATCH_PLATFORM_WINDOWS)
#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+#include <cassert>
+#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace Progmasoft::Catch3::Runner
+{
+ /// A failure with a message written for the person who has to act on it.
+ class Error final
+ {
+ public:
+ explicit Error(std::string message)
+ : message_{std::move(message)}
+ {
+ }
+
+ [[nodiscard]] const std::string &
+ Message() const noexcept
+ {
+ return message_;
+ }
+
+ /// The same failure, with what was being done in front of it.
+ [[nodiscard]] Error
+ Context(const std::string &context) const
+ {
+ return Error{context + ": " + message_};
+ }
+
+ private:
+ std::string message_;
+ };
+
+ /// A value, or the error that prevented it.
+ template<typename Value>
+ class [[nodiscard]] Expected final
+ {
+ public:
+ Expected(Value value)
+ : state_{std::in_place_index<0>, std::move(value)}
+ {
+ }
+
+ Expected(Error error)
+ : state_{std::in_place_index<1>, std::move(error)}
+ {
+ }
+
+ [[nodiscard]] explicit
+ operator bool() const noexcept
+ {
+ return state_.index() == 0;
+ }
+
+ [[nodiscard]] Value &
+ operator*() &
+ {
+ assert(*this);
+ return std::get<0>(state_);
+ }
+
+ [[nodiscard]] const Value &
+ operator*() const &
+ {
+ assert(*this);
+ return std::get<0>(state_);
+ }
+
+ [[nodiscard]] Value &&
+ operator*() &&
+ {
+ assert(*this);
+ return std::get<0>(std::move(state_));
+ }
+
+ [[nodiscard]] Value *
+ operator->()
+ {
+ assert(*this);
+ return &std::get<0>(state_);
+ }
+
+ [[nodiscard]] const Value *
+ operator->() const
+ {
+ assert(*this);
+ return &std::get<0>(state_);
+ }
+
+ [[nodiscard]] const Error &
+ GetError() const
+ {
+ assert(!*this);
+ return std::get<1>(state_);
+ }
+
+ private:
+ std::variant<Value, Error> state_;
+ };
+
+ /// Success, or the error that happened instead.
+ class [[nodiscard]] Status final
+ {
+ public:
+ Status() = default;
+
+ Status(Error error)
+ : error_{std::move(error)}
+ {
+ }
+
+ [[nodiscard]] static Status
+ Ok()
+ {
+ return Status{};
+ }
+
+ [[nodiscard]] explicit
+ operator bool() const noexcept
+ {
+ return !error_.has_value();
+ }
+
+ [[nodiscard]] const Error &
+ GetError() const
+ {
+ assert(error_.has_value());
+ return *error_;
+ }
+
+ private:
+ std::optional<Error> error_;
+ };
+} // namespace Progmasoft::Catch3::Runner
+
+#include <filesystem>
+#include <string>
+#include <vector>
+
+namespace Progmasoft::Catch3::Runner
+{
+ /// What Git says about the checkout the tests come from.
+ struct Changes final
+ {
+ std::filesystem::path root;
+ std::string commit;
+ /// Changed since the revision, staged or not, and untracked files;
+ /// absolute paths.
+ std::vector<std::filesystem::path> files;
+ };
+
+ /// The changes since `revision` (HEAD when empty) in the checkout that
+ /// holds `directory`. An error when Git or the checkout is missing.
+ [[nodiscard]] Expected<Changes>
+ ReadChanges(const std::filesystem::path &directory,
+ const std::string &revision);
+
+ /// The commit checked out in the checkout that holds `directory`, or
+ /// empty.
+ [[nodiscard]] std::string
+ CurrentCommit(const std::filesystem::path &directory);
+
+ /// Whether two spellings name the same source file: equal after
+ /// normalisation, or one a relative path the other ends with.
+ [[nodiscard]] bool
+ SameSourceFile(const std::filesystem::path &left,
+ const std::filesystem::path &right);
+} // namespace Progmasoft::Catch3::Runner
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace Progmasoft::Catch3
+{
+ /// How one attempt at a test ended.
+ enum class Outcome : std::uint8_t
+ {
+ kPassed,
+ /// An assertion failed, or the test threw.
+ kFailed,
+ /// It failed, but `[!shouldfail]` or `[!mayfail]` allows that.
+ kFailedAsExpected,
+ /// `SKIP` was called.
+ kSkipped,
+ /// The process running it died: a signal, a structured exception or
+ /// an exit in the middle of the test.
+ kCrashed,
+ /// It ran past its time limit and was stopped.
+ kTimedOut,
+ /// A test it depends on did not pass, so it was not run.
+ kBlocked,
+ /// The run stopped before reaching it.
+ kNotRun,
+ /// It passed last time and nothing it depends on changed, so the
+ /// incremental run kept that result.
+ kUpToDate,
+ };
+
+ /// Why a failure happened, as far as the runner can tell.
+ enum class FailureCategory : std::uint8_t
+ {
+ kNone,
+ /// A REQUIRE or CHECK expression was false.
+ kAssertion,
+ /// An exception escaped the test or an assertion.
+ kUnexpectedException,
+ /// REQUIRE_THROWS and its kind did not see the expected exception.
+ kMissingException,
+ /// FAIL or FAIL_CHECK.
+ kExplicitFailure,
+ /// A signal or structured exception inside the test.
+ kCrash,
+ kTimeout,
+ /// The worker could not run the test at all, for example because it
+ /// failed to start.
+ kInfrastructure,
+ /// A test it depends on failed.
+ kDependency,
+ };
+
+ /// How a failure relates to earlier runs.
+ enum class FailureHistory : std::uint8_t
+ {
+ /// There is no history to compare with.
+ kUnknown,
+ /// The test passed in the previous run.
+ kNew,
+ /// The test failed in the previous run too.
+ kPersistent,
+ /// The test passes and fails with the same inputs.
+ kIntermittent,
+ };
+
+ /// One failed or reported assertion.
+ struct AssertionRecord final
+ {
+ std::string file;
+ std::size_t line = 0;
+ /// The macro, such as REQUIRE.
+ std::string macro;
+ /// The expression as written.
+ std::string expression;
+ /// The expression with its operands' values.
+ std::string expansion;
+ std::string message;
+ /// INFO, CAPTURE and UNSCOPED_INFO messages in force.
+ std::vector<std::string> context;
+ /// The sections the assertion was in, outermost first.
+ std::vector<std::string> sections;
+ FailureCategory category = FailureCategory::kAssertion;
+ bool passed = false;
+ /// The failure is allowed by `[!mayfail]` or `[!shouldfail]`.
+ bool allowed = false;
+ };
+
+ /// The statistics of one BENCHMARK.
+ struct BenchmarkRecord final
+ {
+ std::string name;
+ std::size_t samples = 0;
+ std::size_t iterations = 0;
+ double meanNanoseconds = 0.0;
+ double lowMeanNanoseconds = 0.0;
+ double highMeanNanoseconds = 0.0;
+ double standardDeviationNanoseconds = 0.0;
+ double outlierVariance = 0.0;
+ };
+
+ /// One section entered while a test ran, with its timing.
+ struct SectionRecord final
+ {
+ std::string name;
+ std::string file;
+ std::size_t line = 0;
+ /// Microseconds since the start of the attempt.
+ std::int64_t startMicroseconds = 0;
+ /// -1 for a section that never ended: the attempt crashed or timed
+ /// out inside it.
+ std::int64_t durationMicroseconds = 0;
+ /// 0 for a section directly in the test case.
+ std::size_t depth = 0;
+ };
+
+ /// One run of a test in a worker. A test may be tried several times:
+ /// retries, repeats and the probes that look for an order dependency.
+ struct Attempt final
+ {
+ Outcome outcome = Outcome::kNotRun;
+ FailureCategory category = FailureCategory::kNone;
+ std::size_t number = 1;
+ /// The worker that ran it, and the tests it ran before, in order.
+ std::size_t worker = 0;
+ std::vector<std::string> precededBy;
+ bool freshProcess = false;
+ std::uint32_t seed = 0;
+ std::int64_t startMicroseconds = 0;
+ std::int64_t durationMicroseconds = 0;
+ std::size_t assertionsPassed = 0;
+ std::size_t assertionsFailed = 0;
+ std::vector<AssertionRecord> assertions;
+ std::vector<BenchmarkRecord> benchmarks;
+ std::vector<SectionRecord> sections;
+ /// What the test wrote to std::cout and std::cerr, captured.
+ std::string standardOutput;
+ std::string standardError;
+ /// What reached the worker's own streams while the test ran, such as
+ /// printf output or a sanitizer report.
+ std::string processOutput;
+ /// The fatal condition the worker reported before it died.
+ std::string fatalMessage;
+ /// How the worker ended, for a crash.
+ std::string exitDescription;
+ /// The last assertion started before a crash or a timeout.
+ std::string lastLocation;
+ /// The directory the test had for its temporary files; kept when the
+ /// attempt failed, so its contents can be inspected.
+ std::string temporaryDirectory;
+ };
+
+ /// One test of the run and everything the runner learned about it.
+ struct TestResult final
+ {
+ /// Identifies the test across runs and builds: its name, with its
+ /// class and, when names repeat, its tags. History is kept by it.
+ std::string id;
+ std::string name;
+ std::string className;
+ std::string file;
+ std::size_t line = 0;
+ std::vector<std::string> tags;
+ /// The verdict over all attempts.
+ Outcome outcome = Outcome::kNotRun;
+ FailureCategory category = FailureCategory::kNone;
+ FailureHistory history = FailureHistory::kUnknown;
+ /// It failed and then passed with the same inputs.
+ bool flaky = false;
+ /// It is in quarantine, so its failure does not fail the run.
+ bool quarantined = false;
+ /// Why it was selected, when not every test runs: "failed last
+ /// run", "covers a changed file", "dependency of ...".
+ std::string selectionReason;
+ /// A stable identifier of the failure: its category, location and
+ /// message with numbers removed. Tests that share it probably share a
+ /// cause.
+ std::string signature;
+ /// What the runner found out about the failure, one finding per
+ /// entry, most useful first.
+ std::vector<std::string> analysis;
+ /// A command line that runs the failure again the way it happened.
+ std::string replayCommand;
+ std::vector<Attempt> attempts;
+ std::int64_t predictedDurationMicroseconds = 0;
+ double predictedFailureProbability = 0.0;
+
+ /// The sum of the attempts' durations.
+ [[nodiscard]] std::int64_t
+ DurationMicroseconds() const noexcept;
+
+ /// Whether the result fails the run.
+ [[nodiscard]] bool
+ FailsRun() const noexcept;
+ };
+
+ /// The totals of a run, by verdict.
+ struct RunSummary final
+ {
+ std::size_t tests = 0;
+ std::size_t passed = 0;
+ std::size_t failed = 0;
+ std::size_t failedAsExpected = 0;
+ std::size_t skipped = 0;
+ std::size_t crashed = 0;
+ std::size_t timedOut = 0;
+ std::size_t blocked = 0;
+ std::size_t notRun = 0;
+ std::size_t upToDate = 0;
+ std::size_t flaky = 0;
+ std::size_t quarantined = 0;
+ std::size_t attempts = 0;
+ std::size_t assertionsPassed = 0;
+ std::size_t assertionsFailed = 0;
+ /// Wall-clock time of the run, and the time its tests took added up;
+ /// their ratio is the speed-up of running in parallel.
+ std::int64_t wallMicroseconds = 0;
+ std::int64_t testMicroseconds = 0;
+ };
+
+ /// A whole run of one test binary.
+ struct RunResult final
+ {
+ std::string binary;
+ std::uint32_t seed = 0;
+ std::size_t workers = 0;
+ /// The shard, when the run was one of several.
+ unsigned shardIndex = 0;
+ unsigned shardCount = 1;
+ /// The tests that matched before selection narrowed them.
+ std::size_t candidates = 0;
+ std::vector<TestResult> tests;
+ /// Test specs that matched nothing.
+ std::vector<std::string> unmatchedSpecs;
+ /// Messages about the run itself, such as a history that could not
+ /// be read.
+ std::vector<std::string> notes;
+ std::int64_t startUnixMilliseconds = 0;
+ std::int64_t wallMicroseconds = 0;
+ };
+
+ [[nodiscard]] RunSummary
+ Summarize(const RunResult &run);
+
+ /// The name an outcome has in reports: "passed", "timed-out", ...
+ [[nodiscard]] const char *
+ OutcomeName(Outcome outcome) noexcept;
+
+ [[nodiscard]] std::optional<Outcome>
+ OutcomeFromName(const std::string &name) noexcept;
+
+ [[nodiscard]] const char *
+ CategoryName(FailureCategory category) noexcept;
+
+ [[nodiscard]] std::optional<FailureCategory>
+ CategoryFromName(const std::string &name) noexcept;
+
+ [[nodiscard]] const char *
+ HistoryName(FailureHistory history) noexcept;
+
+ /// The failure of an attempt as text: each failed assertion with its
+ /// expansion, messages and sections, or what is known about a crash or a
+ /// timeout. Empty for an attempt that did not fail.
+ [[nodiscard]] std::string
+ DescribeFailure(const Attempt &attempt);
+
+ /// The first line of `DescribeFailure`, for a summary.
+ [[nodiscard]] std::string
+ SummarizeFailure(const Attempt &attempt);
+
+ /// Whether an outcome counts as a failure of the attempt.
+ [[nodiscard]] bool
+ IsFailure(Outcome outcome) noexcept;
+} // namespace Progmasoft::Catch3
+#include <chrono>
+#include <cstdint>
+#include <filesystem>
+#include <map>
+#include <optional>
+#include <string>
+#include <vector>
+
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace Progmasoft::Catch3::Runner
+{
+ class JsonValue;
+
+ /// The members of an object, in the order they were added or read.
+ using JsonMembers = std::vector<std::pair<std::string, JsonValue>>;
+
+ /// One JSON value: the messages between the runner and its workers, the
+ /// reports, the history and the coverage exports of LLVM are all JSON.
+ ///
+ /// Numbers are kept as `double`, which represents every integer the
+ /// runner writes (counts, microseconds, 32-bit seeds) exactly.
+ class JsonValue final
+ {
+ public:
+ enum class Kind : std::uint8_t
+ {
+ kNull,
+ kBoolean,
+ kNumber,
+ kString,
+ kArray,
+ kObject,
+ };
+
+ JsonValue() = default;
+ JsonValue(std::nullptr_t) {}
+ JsonValue(bool value);
+ JsonValue(double value);
+ JsonValue(int value);
+ JsonValue(unsigned value);
+ JsonValue(long value);
+ JsonValue(unsigned long value);
+ JsonValue(long long value);
+ JsonValue(unsigned long long value);
+ JsonValue(const char *value);
+ JsonValue(std::string value);
+ JsonValue(std::string_view value);
+
+ JsonValue(const JsonValue &other);
+ JsonValue(JsonValue &&other) noexcept;
+ JsonValue &
+ operator=(const JsonValue &other);
+ JsonValue &
+ operator=(JsonValue &&other) noexcept;
+ ~JsonValue();
+
+ [[nodiscard]] static JsonValue
+ Array(std::vector<JsonValue> items = {});
+
+ [[nodiscard]] static JsonValue
+ Object(JsonMembers members = {});
+
+ [[nodiscard]] Kind
+ GetKind() const noexcept
+ {
+ return kind_;
+ }
+
+ [[nodiscard]] bool
+ IsNull() const noexcept
+ {
+ return kind_ == Kind::kNull;
+ }
+
+ [[nodiscard]] bool
+ IsObject() const noexcept
+ {
+ return kind_ == Kind::kObject;
+ }
+
+ [[nodiscard]] bool
+ IsArray() const noexcept
+ {
+ return kind_ == Kind::kArray;
+ }
+
+ [[nodiscard]] bool
+ IsString() const noexcept
+ {
+ return kind_ == Kind::kString;
+ }
+
+ [[nodiscard]] bool
+ IsNumber() const noexcept
+ {
+ return kind_ == Kind::kNumber;
+ }
+
+ /// The value as a Boolean; anything else reads as `fallback`.
+ [[nodiscard]] bool
+ AsBoolean(bool fallback = false) const noexcept;
+
+ [[nodiscard]] double
+ AsNumber(double fallback = 0.0) const noexcept;
+
+ [[nodiscard]] std::int64_t
+ AsInteger(std::int64_t fallback = 0) const noexcept;
+
+ [[nodiscard]] std::uint64_t
+ AsUnsigned(std::uint64_t fallback = 0) const noexcept;
+
+ /// The text of a string; anything else reads as the empty string.
+ [[nodiscard]] const std::string &
+ AsString() const noexcept;
+
+ /// The items of an array; anything else has none.
+ [[nodiscard]] const std::vector<JsonValue> &
+ Items() const noexcept;
+
+ [[nodiscard]] std::vector<JsonValue> &
+ MutableItems();
+
+ /// The members of an object; anything else has none.
+ [[nodiscard]] const JsonMembers &
+ Members() const noexcept;
+
+ [[nodiscard]] JsonMembers &
+ MutableMembers();
+
+ /// The member named `key`, or null when there is none.
+ [[nodiscard]] const JsonValue &
+ operator[](std::string_view key) const noexcept;
+
+ /// The member named `key` of an object, added when missing.
+ JsonValue &
+ Set(std::string_view key, JsonValue value);
+
+ /// Appends to an array.
+ void
+ Append(JsonValue value);
+
+ /// The compact JSON text of the value, on one line.
+ [[nodiscard]] std::string
+ ToString() const;
+
+ /// The JSON text with two spaces of indentation per level.
+ [[nodiscard]] std::string
+ ToPrettyString() const;
+
+ private:
+ void
+ Write(std::string &output, int indent, int depth) const;
+
+ void
+ Destroy() noexcept;
+
+ void
+ CopyFrom(const JsonValue &other);
+
+ void
+ MoveFrom(JsonValue &&other) noexcept;
+
+ Kind kind_ = Kind::kNull;
+ bool boolean_ = false;
+ double number_ = 0.0;
+ std::string string_;
+ std::unique_ptr<std::vector<JsonValue>> items_;
+ std::unique_ptr<JsonMembers> members_;
+ };
+
+ /// Reads one JSON document; whitespace may surround it.
+ [[nodiscard]] Expected<JsonValue>
+ ParseJson(std::string_view text);
+
+ /// Appends `text` to `output` as a JSON string literal.
+ void
+ AppendJsonString(std::string &output, std::string_view text);
+} // namespace Progmasoft::Catch3::Runner
+
+namespace Progmasoft::Catch3::Runner
+{
+ /// One result of one test in one run.
+ struct TestRecord final
+ {
+ std::int64_t run = 0;
+ Outcome outcome = Outcome::kNotRun;
+ FailureCategory category = FailureCategory::kNone;
+ std::int64_t durationMicroseconds = 0;
+ std::size_t attempts = 0;
+ bool flaky = false;
+ std::string signature;
+ /// What the test's result depended on; equal fingerprints mean the
+ /// same inputs.
+ std::string fingerprint;
+ };
+
+ /// How to run a failure again the way it happened.
+ struct ReplayRecord final
+ {
+ std::int64_t run = 0;
+ std::uint32_t seed = 0;
+ /// The tests the worker ran before, in order.
+ std::vector<std::string> precededBy;
+ bool freshProcess = false;
+ };
+
+ struct QuarantineRecord final
+ {
+ std::int64_t since = 0;
+ std::string reason;
+ };
+
+ /// Everything remembered about one test.
+ struct TestHistory final
+ {
+ /// The most recent last.
+ std::vector<TestRecord> records;
+ std::optional<QuarantineRecord> quarantine;
+ std::optional<ReplayRecord> lastFailure;
+
+ [[nodiscard]] const TestRecord *
+ Last() const noexcept;
+
+ /// A percentile of the durations of recent runs that finished on
+ /// their own; nothing without such runs.
+ [[nodiscard]] std::optional<std::int64_t>
+ DurationPercentile(double fraction) const;
+
+ /// The probability that the next run fails: a failure rate in which
+ /// each older run counts less, starting from a prior for tests with
+ /// little history.
+ [[nodiscard]] double
+ FailureProbability() const;
+
+ /// Flaky results, and failures that alternate with passes on the
+ /// same fingerprint, among the last `window` runs.
+ [[nodiscard]] std::size_t
+ FlakyEvents(std::size_t window) const;
+
+ /// Passes in a row, counting back from the latest run.
+ [[nodiscard]] std::size_t
+ PassStreak() const;
+ };
+
+ /// One run, as the history remembers it.
+ struct RunRecord final
+ {
+ std::int64_t id = 0;
+ std::int64_t unixMilliseconds = 0;
+ std::string commit;
+ std::string binaryFingerprint;
+ std::uint32_t seed = 0;
+ std::size_t tests = 0;
+ std::size_t failed = 0;
+ std::int64_t wallMicroseconds = 0;
+ };
+
+ /// When to put a test into quarantine and let it out.
+ struct QuarantinePolicy final
+ {
+ std::size_t window = 20;
+ std::size_t flakyEventsToEnter = 2;
+ std::size_t passesToLeave = 10;
+ };
+
+ /// The results of earlier runs of one test binary.
+ ///
+ /// It lives in one JSON file. Several runs of the same binary may finish
+ /// at the same time, so `Commit` changes the file under a lock: it reads
+ /// it again, adds its run and replaces it in one rename.
+ class History final
+ {
+ public:
+ History() = default;
+
+ /// Reads the file; a missing file is an empty history.
+ [[nodiscard]] static Expected<History>
+ Load(const std::filesystem::path &file);
+
+ /// Adds `run` to the history in `file`, updates quarantine and
+ /// returns the history as written.
+ [[nodiscard]] static Expected<History>
+ Commit(const std::filesystem::path &file,
+ const RunResult &run,
+ const RunRecord &record,
+ const std::map<std::string, std::string> &fingerprints,
+ const QuarantinePolicy &policy,
+ std::chrono::milliseconds lockWait = std::chrono::seconds(10));
+
+ [[nodiscard]] const TestHistory *
+ Find(const std::string &key) const;
+
+ [[nodiscard]] const std::map<std::string, TestHistory> &
+ Tests() const noexcept
+ {
+ return tests_;
+ }
+
+ [[nodiscard]] const std::vector<RunRecord> &
+ Runs() const noexcept
+ {
+ return runs_;
+ }
+
+ [[nodiscard]] JsonValue
+ ToJson() const;
+
+ [[nodiscard]] static Expected<History>
+ FromJson(const JsonValue &value);
+
+ /// The key a test result is filed under.
+ [[nodiscard]] static std::string
+ KeyOf(const TestResult &test);
+
+ private:
+ void
+ Apply(const RunResult &run,
+ RunRecord record,
+ const std::map<std::string, std::string> &fingerprints,
+ const QuarantinePolicy &policy);
+
+ std::int64_t nextRun_ = 1;
+ std::vector<RunRecord> runs_;
+ std::map<std::string, TestHistory> tests_;
+ };
+
+ /// The file the history of a binary is kept in under `directory`.
+ [[nodiscard]] std::filesystem::path
+ HistoryFile(const std::filesystem::path &directory,
+ const std::string &binaryName);
+} // namespace Progmasoft::Catch3::Runner
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+#include <chrono>
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <vector>
+
+
+namespace Catch
+{
+ class IConfig;
+} // namespace Catch
+
+namespace Progmasoft::Catch3::Runner
+{
+ /// One registered test and what its tags tell the runner.
+ struct TestEntry final
+ {
+ /// The position in registration order, the same in every process
+ /// started from one binary.
+ std::size_t index = 0;
+ /// A name that identifies the test across runs and builds: the test
+ /// name, with its class when it has one, and its tags when another
+ /// test has the same name.
+ std::string key;
+ std::string name;
+ std::string className;
+ std::string file;
+ std::size_t line = 0;
+ std::vector<std::string> tags;
+ bool hidden = false;
+ bool benchmark = false;
+ bool mayFail = false;
+ bool shouldFail = false;
+
+ // From the tags the runner reserves.
+ /// `[!serial]`: nothing else runs at the same time.
+ bool serial = false;
+ /// `[!isolated]`: runs in a process of its own.
+ bool isolated = false;
+ /// `[!quarantine]`: in quarantine whatever its history says.
+ bool quarantined = false;
+ /// `[!resource:NAME]`: tests sharing a resource never overlap.
+ std::vector<std::string> resources;
+ /// `[!depends:NAME]`: runs after these tests, and only if they pass.
+ std::vector<std::string> dependsOn;
+ /// `[!timeout:SECONDS]`.
+ std::optional<std::chrono::milliseconds> timeout;
+ /// `[!retries:N]`.
+ std::optional<unsigned> retries;
+ };
+
+ /// The tests registered in this binary.
+ class Catalog final
+ {
+ public:
+ /// Reads the test registry. Problems with a reserved tag, such as a
+ /// timeout that is not a number, are errors.
+ [[nodiscard]] static Expected<Catalog>
+ Load();
+
+ /// A catalog of the given tests, without a registry behind it: the
+ /// tests cannot run, but they can be planned. Each entry's index
+ /// becomes its position, and an empty key becomes its name.
+ [[nodiscard]] static Catalog
+ FromEntries(std::vector<TestEntry> entries);
+
+ [[nodiscard]] const std::vector<TestEntry> &
+ Tests() const noexcept
+ {
+ return tests_;
+ }
+
+ [[nodiscard]] const std::vector<Catch::TestCaseHandle> &
+ Handles() const noexcept
+ {
+ return handles_;
+ }
+
+ /// The test of a key or of a name; null when there is none.
+ [[nodiscard]] const TestEntry *
+ Find(const std::string &keyOrName) const;
+
+ /// A digest of every key in order. A worker reports it, so the runner
+ /// knows the worker sees the same tests.
+ [[nodiscard]] const std::string &
+ Identity() const noexcept
+ {
+ return identity_;
+ }
+
+ /// The tests the specs of `config` select, in registration order, as
+ /// Catch2 selects them: hidden tests only when a spec names them.
+ /// Specs that matched nothing are added to `unmatched`.
+ [[nodiscard]] std::vector<std::size_t>
+ Match(const Catch::IConfig &config,
+ std::vector<std::string> &unmatched) const;
+
+ private:
+ std::vector<TestEntry> tests_;
+ std::vector<Catch::TestCaseHandle> handles_;
+ std::string identity_;
+ };
+} // namespace Progmasoft::Catch3::Runner
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
+
+
+#include <chrono>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace Progmasoft::Catch3::Runner
+{
+ /// What the command line asks for.
+ enum class Mode : std::uint8_t
+ {
+ kRun,
+ kListTests,
+ kListTags,
+ kAnalytics,
+ kHelp,
+ kVersion,
+ /// Internal: run tests on behalf of a runner, over the protocol.
+ kWorker,
+ };
+
+ enum class ListFormat : std::uint8_t
+ {
+ kText,
+ kNames,
+ kJson,
+ };
+
+ /// Which of the matching tests to run.
+ enum class Selection : std::uint8_t
+ {
+ /// Every matching test.
+ kAll,
+ /// Those that failed, crashed, timed out or were flaky last time.
+ kFailed,
+ /// Those defined in, or covering, files changed since a revision.
+ kChanged,
+ /// Those without a recorded result.
+ kNew,
+ /// Changed, failed and new tests, and those most likely to fail.
+ kSmart,
+ };
+
+ enum class Order : std::uint8_t
+ {
+ /// Most likely to fail per second of running time first.
+ kPriority,
+ kDeclared,
+ kName,
+ kRandom,
+ };
+
+ enum class RetryMode : std::uint8_t
+ {
+ kOff,
+ /// Retry only what may pass on a second try: crashes, timeouts,
+ /// tests with a flaky history and failures that do not repeat.
+ kAuto,
+ /// Retry every failure up to the limit.
+ kAlways,
+ };
+
+ enum class QuarantineMode : std::uint8_t
+ {
+ /// Tests that keep