Browse Source

Set timeouts on test execution

default_compile_flags
vector-of-bool 4 years ago
parent
commit
1d64689ae2
6 changed files with 74 additions and 20 deletions
  1. +1
    -1
      src/dds/build/builder.cpp
  2. +8
    -3
      src/dds/build/plan/exe.cpp
  3. +17
    -1
      src/dds/proc.hpp
  4. +16
    -5
      src/dds/proc.nix.cpp
  5. +27
    -5
      src/dds/proc.win.cpp
  6. +5
    -5
      tools/gcc-9.next.jsonc

+ 1
- 1
src/dds/build/builder.cpp View File

@@ -24,7 +24,7 @@ struct state {
};

void log_failure(const test_failure& fail) {
spdlog::error("Test '{}' failed! [exitted {}]", fail.executable_path.string(), fail.retc);
spdlog::error("Test '{}' failed! [exited {}]", fail.executable_path.string(), fail.retc);
if (fail.signal) {
spdlog::error("Test execution received signal {}", fail.signal);
}

+ 8
- 3
src/dds/build/plan/exe.cpp View File

@@ -78,13 +78,18 @@ std::optional<test_failure> link_executable_plan::run_test(build_env_ref env) co
auto exe_path = calc_executable_path(env);
auto msg = fmt::format("Run test: {:30}", fs::relative(exe_path, env.output_root).string());
spdlog::info(msg);
auto&& [dur, res]
= timed<std::chrono::microseconds>([&] { return run_proc({exe_path.string()}); });
using namespace std::chrono_literals;
auto&& [dur, res] = timed<std::chrono::microseconds>(
[&] { return run_proc({.command = {exe_path.string()}, .timeout = 10s}); });

if (res.okay()) {
spdlog::info("{} - PASSED - {:>9n}μs", msg, dur.count());
return std::nullopt;
} else {
spdlog::error("{} - FAILED - {:>9n}μs [exited {}]", msg, dur.count(), res.retc);
auto exit_msg = fmt::format(res.signal ? "signalled {}" : "exited {}",
res.signal ? res.signal : res.retc);
auto fail_str = res.timed_out ? "TIMEOUT" : "FAILED ";
spdlog::error("{} - {} - {:>9n}μs [{}]", msg, fail_str, dur.count(), exit_msg);
test_failure f;
f.executable_path = exe_path;
f.output = res.output;

+ 17
- 1
src/dds/proc.hpp View File

@@ -1,5 +1,7 @@
#pragma once

#include <chrono>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -25,11 +27,25 @@ std::string quote_command(const Container& c) {
struct proc_result {
int signal = 0;
int retc = 0;
bool timed_out = false;
std::string output;

bool okay() const noexcept { return retc == 0 && signal == 0; }
};

proc_result run_proc(const std::vector<std::string>& args);
struct proc_options {
std::vector<std::string> command;

/**
* Timeout for the subprocess, in milliseconds. If zero, will wait forever
*/
std::optional<std::chrono::milliseconds> timeout = std::nullopt;
};

proc_result run_proc(const proc_options& opts);

inline proc_result run_proc(std::vector<std::string> args) {
return run_proc(proc_options{.command = std::move(args)});
}

} // namespace dds

+ 16
- 5
src/dds/proc.nix.cpp View File

@@ -6,6 +6,7 @@
#include <spdlog/spdlog.h>

#include <poll.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

@@ -61,8 +62,8 @@ spawn_child(const std::vector<std::string>& command, int stdout_pipe, int close_

} // namespace

proc_result dds::run_proc(const std::vector<std::string>& command) {
spdlog::debug("Spawning subprocess: {}", quote_command(command));
proc_result dds::run_proc(const proc_options& opts) {
spdlog::debug("Spawning subprocess: {}", quote_command(opts.command));
int stdio_pipe[2] = {};
auto rc = ::pipe(stdio_pipe);
check_rc(rc == 0, "Create stdio pipe for subprocess");
@@ -70,7 +71,7 @@ proc_result dds::run_proc(const std::vector<std::string>& command) {
int read_pipe = stdio_pipe[0];
int write_pipe = stdio_pipe[1];

auto child = spawn_child(command, write_pipe, read_pipe);
auto child = spawn_child(opts.command, write_pipe, read_pipe);

::close(write_pipe);

@@ -80,13 +81,23 @@ proc_result dds::run_proc(const std::vector<std::string>& command) {

proc_result res;

using namespace std::chrono_literals;

auto timeout = opts.timeout;
while (true) {
rc = ::poll(&stdio_fd, 1, -1);
rc = ::poll(&stdio_fd, 1, static_cast<int>(timeout.value_or(-1ms).count()));
if (rc && errno == EINTR) {
errno = 0;
continue;
}
check_rc(rc > 0, "Failed in poll()");
if (rc == 0) {
// Timeout!
::kill(child, SIGINT);
timeout = std::nullopt;
res.timed_out = true;
spdlog::debug("Subprocess [{}] timed out", quote_command(opts.command));
continue;
}
std::string buffer;
buffer.resize(1024);
auto nread = ::read(stdio_fd.fd, buffer.data(), buffer.size());

+ 27
- 5
src/dds/proc.win.cpp View File

@@ -1,6 +1,7 @@
#ifdef _WIN32
#include "./proc.hpp"

#include <neo/assert.hpp>
#include <spdlog/spdlog.h>
#include <wil/resource.h>

@@ -12,6 +13,7 @@
#include <stdexcept>

using namespace dds;
using namespace std::chrono_literals;

namespace {

@@ -21,8 +23,8 @@ namespace {

} // namespace

proc_result dds::run_proc(const std::vector<std::string>& cmd) {
auto cmd_str = quote_command(cmd);
proc_result dds::run_proc(const proc_options& opts) {
auto cmd_str = quote_command(opts.command);

::SECURITY_ATTRIBUTES security = {};
security.bInheritHandle = TRUE;
@@ -36,6 +38,8 @@ proc_result dds::run_proc(const std::vector<std::string>& cmd) {
}

::SetHandleInformation(reader.get(), HANDLE_FLAG_INHERIT, 0);
::COMMTIMEOUTS timeouts;
::GetCommTimeouts(reader.get(), &timeouts);

wil::unique_process_information proc_info;

@@ -50,7 +54,7 @@ proc_result dds::run_proc(const std::vector<std::string>& cmd) {
nullptr,
nullptr,
true,
0,
CREATE_NEW_PROCESS_GROUP,
nullptr,
nullptr,
&startup_info,
@@ -62,11 +66,30 @@ proc_result dds::run_proc(const std::vector<std::string>& cmd) {
writer.reset();

std::string output;
proc_result res;

auto timeout = opts.timeout;
while (true) {
const int buffer_size = 256;
char buffer[buffer_size];
DWORD nread = 0;
okay = ::ReadFile(reader.get(), buffer, buffer_size, &nread, nullptr);
// Reload the timeout on the pipe
timeouts.ReadTotalTimeoutConstant = static_cast<DWORD>(timeout.value_or(0ms).count());
::SetCommTimeouts(reader.get(), &timeouts);
// Read some bytes from the process
okay = ::ReadFile(reader.get(), buffer, buffer_size, &nread, nullptr);
if (!okay && ::GetLastError() == ERROR_TIMEOUT) {
// We didn't read any bytes. Hit the timeout
neo_assert_always(invariant,
nread == 0,
"Didn't expect to read bytes when a timeout was reached",
nread,
timeout->count());
res.timed_out = true;
timeout = std::nullopt;
::GenerateConsoleCtrlEvent(CTRL_C_EVENT, proc_info.dwProcessId);
continue;
}
if (!okay && ::GetLastError() != ERROR_BROKEN_PIPE) {
throw_system_error("Failed while reading from the stdio pipe");
}
@@ -85,7 +108,6 @@ proc_result dds::run_proc(const std::vector<std::string>& cmd) {
throw_system_error("Failed reading exit code of process");
}

proc_result res;
res.retc = rc;
res.output = std::move(output);
return res;

+ 5
- 5
tools/gcc-9.next.jsonc View File

@@ -5,17 +5,17 @@
"cxx_compiler": "g++-9",
"flags": [
"-Werror=return-type",
"-fsanitize=address",
// "-fsanitize=address",
],
"cxx_flags": [
"-fconcepts",
"-std=c++2a",
],
"link_flags": [
// "-static-libgcc",
// "-static-libstdc++"
"-fsanitize=address",
"-fuse-ld=lld",
"-static-libgcc",
"-static-libstdc++"
// "-fsanitize=address",
// "-fuse-ld=lld",
],
"debug": true,
// "optimize": true,

Loading…
Cancel
Save