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
|
// Copyright (c) 2026 Progmasoft.
// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
#pragma once
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "Expected.hpp"
namespace Progmasoft::Catch3::Runner
{
/// How a process ended.
struct ExitStatus final
{
enum class Kind : std::uint8_t
{
/// It returned from `main` or called `exit` with `code`.
kExited,
/// A signal ended it (POSIX); `code` is the signal number.
kSignaled,
/// It ended with a structured exception (Windows); `code` is the
/// exception code, such as 0xC0000005 for an access violation.
kException,
};
Kind kind = Kind::kExited;
std::int64_t code = 0;
[[nodiscard]] bool
Succeeded() const noexcept
{
return kind == Kind::kExited && code == 0;
}
/// Whether the process died rather than exiting.
[[nodiscard]] bool
Crashed() const noexcept
{
return kind != Kind::kExited;
}
/// A description such as "exit code 3", "signal 11 (SIGSEGV)" or
/// "exception 0xC0000005 (access violation)".
[[nodiscard]] std::string
Describe() const;
};
/// The end of a pipe this process reads from.
class PipeReader final
{
public:
PipeReader() = default;
explicit PipeReader(std::intptr_t handle) noexcept
: handle_{ handle }
{}
PipeReader(const PipeReader &) = delete;
PipeReader &
operator=(const PipeReader &) = delete;
PipeReader(PipeReader &&other) noexcept;
PipeReader &
operator=(PipeReader &&other) noexcept;
~PipeReader();
/// Reads at most `size` bytes. Returns the number read, 0 at the end
/// of the stream and -1 on an error.
[[nodiscard]] std::int64_t
Read(char *buffer, std::size_t size) noexcept;
void
Close() noexcept;
[[nodiscard]] bool
IsOpen() const noexcept;
private:
std::intptr_t handle_ = -1;
};
/// The end of a pipe this process writes to.
class PipeWriter final
{
public:
PipeWriter() = default;
explicit PipeWriter(std::intptr_t handle) noexcept
: handle_{ handle }
{}
PipeWriter(const PipeWriter &) = delete;
PipeWriter &
operator=(const PipeWriter &) = delete;
PipeWriter(PipeWriter &&other) noexcept;
PipeWriter &
operator=(PipeWriter &&other) noexcept;
~PipeWriter();
/// Writes all of `data`, or reports why it could not.
Status
Write(std::string_view data) noexcept;
void
Close() noexcept;
[[nodiscard]] bool
IsOpen() const noexcept;
private:
std::intptr_t handle_ = -1;
};
/// What to start, and how.
struct ChildOptions final
{
/// The program and its arguments. The program is a path; it is not
/// looked up on `PATH` unless `searchPath` is set.
std::vector<std::string> arguments;
/// Variables added to, or replacing, those of this process.
std::vector<std::pair<std::string, std::string>> environment;
/// The working directory; empty keeps the current one.
std::filesystem::path workingDirectory;
bool searchPath = false;
};
/// A child process with its standard streams connected to pipes.
///
/// The child and every process it starts in turn form one group (a job
/// object on Windows, a process group elsewhere), so `Kill` ends a test
/// together with the helpers it spawned. Pipes are not inherited by
/// any other child, so the end of a stream is seen as soon as its
/// process is gone.
class ChildProcess final
{
public:
ChildProcess(const ChildProcess &) = delete;
ChildProcess &
operator=(const ChildProcess &) = delete;
~ChildProcess();
[[nodiscard]] static Expected<std::unique_ptr<ChildProcess>>
Start(const ChildOptions &options);
/// The child's standard input.
[[nodiscard]] PipeWriter &
Input() noexcept
{
return input_;
}
/// The child's standard output.
[[nodiscard]] PipeReader &
Output() noexcept
{
return output_;
}
/// The child's standard error.
[[nodiscard]] PipeReader &
Errors() noexcept
{
return errors_;
}
/// Ends the child and every process it started. Safe to call more
/// than once and after the child ended.
void
Kill() noexcept;
/// Waits for the child to end.
[[nodiscard]] ExitStatus
Wait() noexcept;
/// Waits at most `timeout`; nothing when the child still runs.
[[nodiscard]] std::optional<ExitStatus>
WaitFor(std::chrono::milliseconds timeout) noexcept;
[[nodiscard]] std::int64_t
Id() const noexcept
{
return id_;
}
private:
ChildProcess() = default;
PipeWriter input_;
PipeReader output_;
PipeReader errors_;
std::int64_t id_ = 0;
std::intptr_t process_ = -1;
std::intptr_t group_ = -1;
std::optional<ExitStatus> exit_;
};
/// The result of a command run to completion.
struct CapturedRun final
{
ExitStatus status;
std::string output;
std::string errors;
};
/// Runs a command with no input and collects its output.
[[nodiscard]] Expected<CapturedRun>
RunAndCapture(const ChildOptions &options);
/// The two streams a worker talks to the runner over.
struct WorkerStreams final
{
PipeReader commands;
PipeWriter events;
};
/// Takes standard input and output of this process for the runner
/// protocol. Afterwards standard input reads nothing and standard output
/// goes to standard error, so what a test prints cannot corrupt a
/// message.
[[nodiscard]] Expected<WorkerStreams>
TakeStandardStreams();
/// The path of the running executable.
[[nodiscard]] std::filesystem::path
CurrentExecutable(const char *argumentZero);
/// The number of hardware threads, at least 1.
[[nodiscard]] unsigned
HardwareThreads() noexcept;
/// The value of an environment variable; empty when it is not set.
[[nodiscard]] std::string
ReadEnvironment(const char *name);
[[nodiscard]] bool
HasEnvironment(const char *name);
/// Whether standard output is a terminal.
[[nodiscard]] bool
IsStandardOutputTerminal() noexcept;
/// Turns on ANSI colour sequences on a Windows console; true when the
/// terminal shows them.
[[nodiscard]] bool
EnableTerminalColours() noexcept;
/// Keeps a crash of a worker on Windows from opening an error-reporting
/// dialog that would wait for a person. Elsewhere it does nothing.
void
QuietCrashReporting() noexcept;
/// The id of this process.
[[nodiscard]] std::int64_t
CurrentProcessId() noexcept;
/// Writes `text` to descriptor 2 directly, past any stream buffer or
/// redirection, as far as it goes; for use while the process exits.
void
WriteStandardError(std::string_view text) noexcept;
} // namespace Progmasoft::Catch3::Runner
|