Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions include/asmgrader/api/process_statistics.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// \file
/// Provides an API for basic process statistics
#pragma once

#include <filesystem>
#include <string>
#include <string_view>
#include <vector>

#include <sched.h>

namespace asmgrader {

/// Obtains statistics of a process with a given PID.
/// Provides various accessors returning POD types.
///
/// For now, features are extremely limited.
///
/// Makes use of proc(5)
class ProcessStats
{
public:
/// Open File Descriptor(s) Info POD
struct OpenFds
{
std::vector<int> fds;
};

explicit ProcessStats(pid_t pid);

OpenFds open_fds() const { return open_fds_; }

private:
std::filesystem::path procfs_path(std::string_view suffix) const;

OpenFds get_open_fds() const;

pid_t pid_;

OpenFds open_fds_;
};

} // namespace asmgrader
57 changes: 57 additions & 0 deletions include/asmgrader/api/tempfile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/// \file
/// Provides an API for creating and managing temporary files in the context of tests
#pragma once

#include <asmgrader/common/aliases.hpp>
#include <asmgrader/common/class_traits.hpp>
#include <asmgrader/common/expected.hpp>
#include <asmgrader/logging.hpp>

#include <gsl/pointers>

#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>

namespace asmgrader {

/// Interface onto a temporary file.
class TempFile : NonMovable
{
public:
explicit TempFile(bool create = true);
explicit TempFile(u16 perms);
~TempFile() noexcept;

std::string read_all();
void write(std::string_view str);
void truncate();

bool exists() const;

std::filesystem::path path() const { return file_info_.path; }

std::string path_str() const { return file_info_.path.string(); }

[[nodiscard]] static std::filesystem::path unique_path();

/// Remove (delete) a file. Intended for use with files created based on
/// \ref unique_path, but could be used to safely remove any arbitrary file.
static Expected<> remove(const std::filesystem::path& path);

private:
struct FileInfo
{
std::filesystem::path path;
std::fstream handle;
};

[[nodiscard]] static FileInfo generate_unique_file();

bool ensure_created();

FileInfo file_info_;
};

} // namespace asmgrader
11 changes: 10 additions & 1 deletion include/asmgrader/api/test_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <asmgrader/api/asm_buffer.hpp>
#include <asmgrader/api/asm_function.hpp>
#include <asmgrader/api/asm_symbol.hpp>
#include <asmgrader/api/process_statistics.hpp>
#include <asmgrader/api/registers_state.hpp>
#include <asmgrader/api/requirement.hpp>
#include <asmgrader/common/aliases.hpp>
Expand Down Expand Up @@ -107,7 +108,15 @@ class TestContext
AsmFunction<Func> find_function(std::string name);

/// Run the program normally from `_start`, stopping at the first exit(2) or exit_group(2) syscall invocation
RunResult run();
Result<RunResult> run();

/// Run the program from `_start`, stopping at the first syscall matching syscallnr
/// OR the first exit(2) or exit_group(2) syscall invocation [whichever happens first]
Result<RunResult> run_until(u64 syscallnr);

/// Obtain statistics for the subprocess being tested
/// Data is more limited when the process is not stopped!
ProcessStats stats();

private:
bool require_impl(bool condition, const std::string& description,
Expand Down
1 change: 1 addition & 0 deletions include/asmgrader/asmgrader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <asmgrader/api/asm_data.hpp> // IWYU pragma: export
#include <asmgrader/api/asm_function.hpp> // IWYU pragma: export
#include <asmgrader/api/asm_symbol.hpp> // IWYU pragma: export
#include <asmgrader/api/tempfile.hpp> // IWYU pragma: export
#include <asmgrader/api/test_base.hpp> // IWYU pragma: export
#include <asmgrader/api/test_context.hpp> // IWYU pragma: export
#include <asmgrader/common/aliases.hpp> // IWYU pragma: export
Expand Down
42 changes: 22 additions & 20 deletions include/asmgrader/common/linux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ inline Expected<ssize_t> write(int fd, std::string_view data) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("write(fd={}, data={:?}, size={}) failed: '{}'", fd, data, data.size(), err);
LOG_TRACE("write(fd={}, data={:?}, size={}) failed: '{}'", fd, data, data.size(), err);
return err;
}

Expand All @@ -71,7 +71,7 @@ inline Expected<std::string> read(int fd, size_t count) { // NOLINT
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("read(fd={}, count={}) failed: '{}'", fd, count, err);
LOG_TRACE("read(fd={}, count={}) failed: '{}'", fd, count, err);

return err;
}
Expand All @@ -92,10 +92,12 @@ inline Expected<> close(int fd) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("close failed: '{}'", err);
LOG_TRACE("close(fd={}) failed: '{}'", fd, err);
return err;
}

LOG_TRACE("close(fd={}) = {}", fd, res);

return {};
}

Expand All @@ -107,12 +109,12 @@ inline Expected<> kill(pid_t pid, int sig) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("kill(pid={}, sig={}) failed: '{}'", pid, sig, err);
LOG_TRACE("kill(pid={}, sig={}) failed: '{}'", pid, sig, err);

return err;
}

LOG_DEBUG("kill(pid={}, sig={}) = {}", pid, sig, res);
LOG_TRACE("kill(pid={}, sig={}) = {}", pid, sig, res);

return {};
}
Expand All @@ -127,7 +129,7 @@ inline Expected<> access(gsl::czstring path, int mode) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("access(path={:?}, mode={}) failed '{}'", path, mode, err);
LOG_TRACE("access(path={:?}, mode={}) failed '{}'", path, mode, err);

return err;
}
Expand Down Expand Up @@ -160,9 +162,9 @@ inline Expected<> execve(const std::string& exec, const std::vector<std::string>
auto err = make_error_code(errno);

if (res == -1) {
LOG_DEBUG("execve failed: '{}'", err);
LOG_TRACE("execve failed: '{}'", err);
} else {
LOG_DEBUG("execve failed (INVALID RETURN CODE = {}): '{}'", res, err);
LOG_TRACE("execve failed (INVALID RETURN CODE = {}): '{}'", res, err);
}

return err;
Expand All @@ -182,7 +184,7 @@ inline Expected<Fork> fork() {

if (res == -1) {
auto err = make_error_code(errno);
LOG_DEBUG("fork failed: '{}'", err);
LOG_TRACE("fork failed: '{}'", err);
return err;
}

Expand All @@ -201,7 +203,7 @@ inline Expected<int> open(const std::string& pathname, int flags, mode_t mode =

if (res == -1) {
auto err = make_error_code(errno);
LOG_DEBUG("open(pathname={:?}, flags={}, mode={}) failed: '{}'", pathname, flags, mode, err);
LOG_TRACE("open(pathname={:?}, flags={}, mode={}) failed: '{}'", pathname, flags, mode, err);
return err;
}

Expand All @@ -217,7 +219,7 @@ inline Expected<off_t> lseek(int fd, off_t offset, int whence) {

if (res == -1) {
auto err = make_error_code(errno);
LOG_DEBUG("lseek failed: '{}'", err);
LOG_TRACE("lseek failed: '{}'", err);
return err;
}

Expand All @@ -233,9 +235,9 @@ inline Expected<> dup2(int oldfd, int newfd) {
auto err = make_error_code(errno);

if (res == -1) {
LOG_DEBUG("dup2 failed: '{}'", err);
LOG_TRACE("dup2 failed: '{}'", err);
} else {
LOG_DEBUG("dup2 failed (INVALID RETURN CODE = {}): '{}'", res, err);
LOG_TRACE("dup2 failed (INVALID RETURN CODE = {}): '{}'", res, err);
}

return err;
Expand All @@ -254,7 +256,7 @@ inline Expected<int> ioctl(int fd, unsigned long request, void* argp) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("ioctl failed: '{}'", err);
LOG_TRACE("ioctl failed: '{}'", err);

return err;
}
Expand All @@ -278,7 +280,7 @@ inline Expected<int> fcntl(int fd, int cmd, std::optional<int> arg = std::nullop
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("fcntl failed: '{}'", err);
LOG_TRACE("fcntl failed: '{}'", err);

return err;
}
Expand All @@ -295,7 +297,7 @@ inline Expected<siginfo_t> waitid(idtype_t idtype, id_t id, int options = WSTOPP
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("waitid(idtype={}, id={}, options={}) failed: '{}'", fmt::underlying(idtype), id, options, err);
LOG_TRACE("waitid(idtype={}, id={}, options={}) failed: '{}'", fmt::underlying(idtype), id, options, err);

return err;
}
Expand All @@ -314,7 +316,7 @@ inline Expected<> raise(int sig) {
if (res == -1) {
auto err = std::error_code(errno, std::system_category());

LOG_DEBUG("raise({}) failed: '{}'", sig, err);
LOG_TRACE("raise({}) failed: '{}'", sig, err);

return err;
}
Expand Down Expand Up @@ -343,7 +345,7 @@ inline Expected<Pipe> pipe2(int flags = 0) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("pipe(..., flags={}) failed: '{}'", flags, err);
LOG_TRACE("pipe(..., flags={}) failed: '{}'", flags, err);

return err;
}
Expand Down Expand Up @@ -378,7 +380,7 @@ inline Expected<long> ptrace(int request, pid_t pid = 0, AddrT addr = NULL, Data
if (errno) {
auto err = make_error_code(errno);

LOG_DEBUG("ptrace(req={}, pid={}, addr={}, data={}) failed: '{}'", request, pid, format_or_unknown(addr),
LOG_TRACE("ptrace(req={}, pid={}, addr={}, data={}) failed: '{}'", request, pid, format_or_unknown(addr),
format_or_unknown(data), err);

return err;
Expand All @@ -399,7 +401,7 @@ inline Expected<struct ::stat> stat(const std::string& pathname) {
if (res == -1) {
auto err = make_error_code(errno);

LOG_DEBUG("stat failed: '{}'", err);
LOG_TRACE("stat failed: '{}'", err);

return err;
}
Expand Down
72 changes: 66 additions & 6 deletions include/asmgrader/subprocess/run_result.hpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,84 @@
#pragma once

#include <fmt/format.h>

#include <string>
#include <string_view>

namespace asmgrader {

class RunResult
{
public:
enum class Kind { Exited, Killed, SignalCaught };

static RunResult make_exited(int code);
static RunResult make_killed(int code);
static RunResult make_signal_caught(int code);
static constexpr RunResult make_exited(int code);
static constexpr RunResult make_killed(int code);
static constexpr RunResult make_signal_caught(int code);

constexpr Kind get_kind() const;
constexpr int get_code() const;

constexpr bool operator==(const RunResult&) const = default;

Kind get_kind() const;
int get_code() const;
std::string str() const;

private:
RunResult(Kind kind, int code);
constexpr RunResult(Kind kind, int code);

Kind kind_;
int code_;
};

constexpr RunResult::RunResult(Kind kind, int code)
: kind_{kind}
, code_{code} {}

constexpr RunResult RunResult::make_exited(int code) {
return {Kind::Exited, code};
}

constexpr RunResult RunResult::make_killed(int code) {
return {Kind::Killed, code};
}

constexpr RunResult RunResult::make_signal_caught(int code) {
return {Kind::SignalCaught, code};
}

constexpr RunResult::Kind RunResult::get_kind() const {
return kind_;
}

constexpr int RunResult::get_code() const {
return code_;
}

static constexpr auto RUN_SUCCESS = RunResult::make_exited(0);

constexpr std::string_view format_as(const RunResult::Kind& from) {
switch (from) {
case RunResult::Kind::Exited:
return "Exited";
case RunResult::Kind::Killed:
return "Killed";
case RunResult::Kind::SignalCaught:
return "SignalCaught";
default:
return "<unknown>";
}
}

inline std::string format_as(const RunResult& from) {
return fmt::format("{}({})", from.get_kind(), from.get_code());
}

constexpr std::string_view str(const RunResult::Kind& from) {
return format_as(from);
}

inline std::string RunResult::str() const {
return fmt::to_string(*this);
}

} // namespace asmgrader
Loading
Loading