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

summaryrefslogtreecommitdiffhomepage
path: root/src/Progmasoft/Runner/SchedulerModel.hpp
blob: 0d5a1c6ce2ee75169324301ee97a92405f1fe384 (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
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
#pragma once

// The state the scheduler keeps of jobs, workers and tests.

#include <Progmasoft/Catch3/Results.hpp>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <vector>

#include "Process.hpp"

namespace Progmasoft::Catch3::Runner::Scheduling
{
    using Clock = std::chrono::steady_clock;

    inline constexpr std::size_t kMaximumRecordedAssertions = 200;
    inline constexpr std::size_t kMaximumProcessOutput = 64 * 1024;
    inline constexpr std::size_t kMaximumProbes = 12;
    inline constexpr std::size_t kStartupFailuresToGiveUp = 3;
    inline constexpr std::chrono::milliseconds kStartupTimeout{ 60000 };

    inline std::int64_t
    Microseconds(Clock::duration duration)
    {
        return std::chrono::duration_cast<std::chrono::microseconds>(duration)
            .count();
    }

    inline void
    AppendBounded(std::string &target, std::string_view text)
    {
        target.append(text);
        if (target.size() > kMaximumProcessOutput)
        {
            // The end of the output says most about a crash.
            target.erase(0, target.size() - kMaximumProcessOutput);
        }
    }

    inline bool
    Satisfies(Outcome outcome)
    {
        return outcome == Outcome::kPassed
               || outcome == Outcome::kFailedAsExpected
               || outcome == Outcome::kUpToDate;
    }

    inline bool
    Passing(Outcome outcome)
    {
        return outcome == Outcome::kPassed
               || outcome == Outcome::kFailedAsExpected
               || outcome == Outcome::kSkipped;
    }

    /// What a worker is asked to do: run tests in order, the last one
    /// being the one the job is about.
    struct Job final
    {
        enum class Kind : std::uint8_t
        {
            /// An attempt of a planned test.
            kAttempt,
            /// A run that tells whether a failure depends on the tests
            /// before it; its result is not an attempt.
            kProbe,
        };

        Kind kind = Kind::kAttempt;
        std::size_t test = 0;
        /// Catalog indices to run first, in the same process.
        std::vector<std::size_t> before;
        bool fresh = false;
        std::optional<std::uint32_t> seed;
        /// For a probe: the tests that ran before it.
        std::vector<std::size_t> probeSequence;
    };

    /// One test being run in a worker.
    struct Running final
    {
        std::size_t catalogIndex = 0;
        bool target = false;
        Attempt attempt;
        Clock::time_point start;
        Clock::time_point deadline = Clock::time_point::max();
        bool fatal = false;
    };

    struct Worker final
    {
        std::size_t id = 0;
        std::unique_ptr<ChildProcess> process;
        std::thread outputReader;
        std::thread errorReader;
        bool ready = false;
        bool dedicated = false;
        bool quitting = false;
        bool outputClosed = false;
        bool errorsClosed = false;
        bool killedForTimeout = false;
        std::optional<std::uint32_t> seed;
        Clock::time_point started;
        std::optional<Job> job;
        /// Index into `job->before`, or its size for the target.
        std::size_t step = 0;
        std::optional<Running> running;
        /// Catalog indices this process has run, in order.
        std::vector<std::size_t> history;
        std::string startupOutput;
    };

    struct Event final
    {
        enum class Kind : std::uint8_t
        {
            kLine,
            kErrors,
            kOutputClosed,
            kErrorsClosed,
        };
        std::size_t worker = 0;
        Kind kind = Kind::kLine;
        std::string text;
    };

    /// The search for the test that leaves state behind.
    struct Bisection final
    {
        enum class Stage : std::uint8_t
        {
            kNone,
            kConfirming,
            kSearching,
            kDone,
        };
        Stage stage = Stage::kNone;
        std::vector<std::size_t> candidates;
        std::vector<std::size_t> probing;
        std::size_t probes = 0;
        std::optional<std::size_t> culprit;
        bool reproduced = false;
    };

    struct TestState final
    {
        TestResult result;
        bool queued = false;
        bool finished = false;
        std::size_t pendingJobs = 0;
        std::size_t retriesUsed = 0;
        std::size_t repeatsLeft = 0;
        Bisection bisection;
    };
} // namespace Progmasoft::Catch3::Runner::Scheduling