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

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Catch3/Results.hpp
blob: 4c6bb1cec5358d8e967d4ad9163c0ab94b66a1ac (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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
#pragma once

#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