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
30 changes: 28 additions & 2 deletions src/arguments.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ enum TdoError tdo_arguments_parse(struct TdoArguments *args, int argc, char **ar
.output = NULL,
.overwrite = false,
.internal_status = NULL,
.format = TDO_FORMAT_HUMAN,
.verbosity = TDO_VERBOSITY_NONE,
.stop_on_first_error = false,
};

if (argc < 1) return TDO_ERROR_ARG_FIRST;
Expand Down Expand Up @@ -46,7 +49,7 @@ enum TdoError tdo_arguments_parse(struct TdoArguments *args, int argc, char **ar
char const *job_str = NULL;
if (strcmp(s, "-j") == 0) {
if (argc <= 1) {
fprintf(stderr, "Missing job argument to '-j'\n");
fprintf(stderr, "Missing argument to '-j'\n");
result = TDO_ERROR_ARG_PARSE;
argc -= 1; argv += 1;
continue;
Expand Down Expand Up @@ -96,7 +99,7 @@ enum TdoError tdo_arguments_parse(struct TdoArguments *args, int argc, char **ar
}
} else if (strcmp(s, "--timeout") == 0) {
if (argc <= 1) {
fprintf(stderr, "Missing timeout argument to '--timeout'\n");
fprintf(stderr, "Missing argument to '--timeout'\n");
result = TDO_ERROR_ARG_PARSE;
} else {
argc -= 1; argv += 1;
Expand All @@ -118,6 +121,29 @@ enum TdoError tdo_arguments_parse(struct TdoArguments *args, int argc, char **ar
args->time_limit = timeout;
}
}
} else if (strcmp(s, "--format") == 0) {
if (argc <= 1) {
fprintf(stderr, "Missing argument to '--format'\n");
result = TDO_ERROR_ARG_PARSE;
} else {
argc -= 1; argv += 1;
char const *format_str = argv[0];

if (strcmp(format_str, "human") == 0) {
args->format = TDO_FORMAT_HUMAN;
} else if (strcmp(format_str, "json") == 0) {
args->format = TDO_FORMAT_JSON;
} else {
fprintf(stderr, "Unknown format argument '%s', see '-h' for options\n", format_str);
result = TDO_ERROR_ARG_PARSE;
}
}
} else if (strcmp(s, "-v") == 0) {
args->verbosity = TDO_VERBOSITY_MINOR;
} else if (strcmp(s, "-vv") == 0) {
args->verbosity = TDO_VERBOSITY_MAJOR;
} else if (strcmp(s, "-x") == 0) {
args->stop_on_first_error = true;
} else {
fprintf(stderr, "Unrecognized argument: '%s'\n", s);
result = TDO_ERROR_ARG_PARSE;
Expand Down
14 changes: 14 additions & 0 deletions src/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@

#define TDO_PROCESS_MAX ((size_t) 2048)

enum TdoFormat {
TDO_FORMAT_HUMAN,
TDO_FORMAT_JSON,
};

enum TdoVerbosity {
TDO_VERBOSITY_NONE,
TDO_VERBOSITY_MINOR,
TDO_VERBOSITY_MAJOR,
};

struct TdoArguments {
size_t processes;
float time_limit;
char const *single_test;
char const *test_file;
char const *output;
char const *internal_status;
enum TdoFormat format;
enum TdoVerbosity verbosity;
bool overwrite;
bool stop_on_first_error;
};

enum TdoError tdo_arguments_parse(struct TdoArguments *args, int argc, char **argv);
Expand Down
8 changes: 8 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ static const char *tdo_help_text =
"Options:\n"
" -t \"TEST_DEFINITION\" Run a single test definition string directly.\n"
" -j [N] Run tests in parallel using N processes (default: 1).\n"
" -x Stop execution immediately on the first test failure.\n"
" --format FMT Select output format: 'human' or 'json' (default: human).\n"
" -v, -vv Set output verbosity level:\n"
" -v: Minor verbosity\n"
" -vv: Major verbosity\n"
" Note: Verbosity only applies to 'human' format.\n"
" -o FILE Write results to the specified FILE.\n"
" -f Force overwrite the output file if it already exists.\n"
" --timeout SECONDS Set a maximum execution time per test (default: 5.0).\n"
Expand Down Expand Up @@ -110,6 +116,8 @@ int main(int argc, char **argv) {
goto error_open_input;
}
file_name = args.test_file;

if (args.internal_status == NULL) fprintf(stderr, "Reading tests from input file '%s'\n", args.test_file);
} else if (args.single_test) {
if (args.internal_status == NULL) fprintf(stderr, "Reading test from command line\n");
file_name = "<cmd>";
Expand Down
1 change: 1 addition & 0 deletions src/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#endif

TdoMonotoneTime tdo_time_get(void);
double tdo_time_between(TdoMonotoneTime end, TdoMonotoneTime start);

FILE *tdo_file_open_exclusive(char const *path, bool overwrite);

Expand Down
7 changes: 7 additions & 0 deletions src/platform/posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ TdoMonotoneTime tdo_time_get(void) {
return time;
}

double tdo_time_between(TdoMonotoneTime end, TdoMonotoneTime start) {
return (
(double)(end.tv_sec - start.tv_sec)
+ (double)(end.tv_nsec - start.tv_nsec) * 1e-9
);
}

FILE *tdo_file_open_exclusive(char const *path, bool overwrite) {
int open_flags = O_WRONLY | O_CREAT;
if (!overwrite) open_flags |= O_EXCL;
Expand Down
59 changes: 28 additions & 31 deletions src/platform/run_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,15 @@ struct TdoRunStatus {
size_t started;
size_t finished;
size_t running;
size_t success;
size_t exit;
size_t timeout;
size_t signal;
size_t error;
size_t success_in_a_row;
bool fork_failed;
bool log_setup_failed;
bool any_failed;
};

enum TdoError tdo_log_drain(struct TdoLog *log, struct TdoArena *arena) {
Expand Down Expand Up @@ -172,27 +179,22 @@ void tdo_run_start_new(struct TdoRunStatus *status, struct TdoArena *arena, stru
status->running += 1;
}

void tdo_run_poll_exit(struct TdoRun *run, struct TdoRunStatus *status, struct TdoArena *arena, FILE *output) {
void tdo_run_poll_exit(struct TdoArguments *args, struct TdoRun *run, struct TdoRunStatus *status, struct TdoArena *arena, FILE *output) {
int return_status;
if (waitpid(run->pid, &return_status, WNOHANG) > 0) {
enum TdoError out_err = tdo_log_drain(&run->out, arena);
enum TdoError err_err = tdo_log_drain(&run->err, arena);
enum TdoError status_err = tdo_log_drain(&run->status, arena);

struct timespec end_time = tdo_time_get();
double duration = tdo_time_between(end_time, run->start_time);

double duration = (
(double)(end_time.tv_sec - run->start_time.tv_sec)
+ (double)(end_time.tv_nsec - run->start_time.tv_nsec) * 1e-9
);

if (status->finished > 0) fprintf(output, ",");
if (out_err == TDO_ERROR_OK && err_err == TDO_ERROR_OK && status_err == TDO_ERROR_OK) {
tdo_run_report_status(run, arena, output, return_status, duration, false);
tdo_run_report_status(args, status, run, arena, output, return_status, duration, false);
} else if (out_err == TDO_ERROR_MEMORY || err_err == TDO_ERROR_MEMORY || status_err == TDO_ERROR_MEMORY) {
tdo_run_report_error(*run->test, output, NULL, "could not allocate space for output", duration);
tdo_run_report_error(args, status, *run->test, output, NULL, "could not allocate space for output", duration);
} else {
tdo_run_report_error(*run->test, output, NULL, "could not read output", duration);
tdo_run_report_error(args, status, *run->test, output, NULL, "could not read output", duration);
}

run->active = false;
Expand Down Expand Up @@ -225,16 +227,12 @@ void tdo_run_poll_event(struct TdoRunStatus *status, struct TdoArena *arena, str

if (err != TDO_ERROR_OK) {
TdoMonotoneTime end_time = tdo_time_get();
double duration = (
(double)(end_time.tv_sec - run->start_time.tv_sec)
+ (double)(end_time.tv_nsec - run->start_time.tv_nsec) * 1e-9
);
double duration = tdo_time_between(end_time, run->start_time);

if (status->finished > 0) fprintf(output, ",");
if (err == TDO_ERROR_MEMORY) {
tdo_run_report_error(*run->test, output, NULL, "could not allocate space for output", duration);
tdo_run_report_error(&args, status, *run->test, output, NULL, "could not allocate space for output", duration);
} else {
tdo_run_report_error(*run->test, output, NULL, "could not read output", duration);
tdo_run_report_error(&args, status, *run->test, output, NULL, "could not read output", duration);
}

kill(run->pid, SIGKILL);
Expand All @@ -255,20 +253,16 @@ void tdo_run_poll_event(struct TdoRunStatus *status, struct TdoArena *arena, str
for (size_t i = 0; i < args.processes; i++) {
struct TdoRun *run = &status->runs[i];
if (run->active) {
double duration = (
(double)(end_time.tv_sec - run->start_time.tv_sec)
+ (double)(end_time.tv_nsec - run->start_time.tv_nsec) * 1e-9
);
tdo_run_report_error(*run->test, output, NULL, "could not poll pipes", duration);
double duration = tdo_time_between(end_time, run->start_time);
tdo_run_report_error(&args, status, *run->test, output, NULL, "could not poll pipes", duration);
status->finished += 1;
run->active = false;
}
}

struct TdoTest *ts = tests.data;
for (size_t i = status->started; i < tests.length; i++) {
if (status->finished > 0) fprintf(output, ",");
tdo_run_report_error(ts[i], output, NULL, "could not poll pipes", -1.0);
tdo_run_report_error(&args, status, ts[i], output, NULL, "could not poll pipes", -1.0);
status->finished += 1;
}
}
Expand All @@ -278,16 +272,12 @@ void tdo_run_poll_event(struct TdoRunStatus *status, struct TdoArena *arena, str
for (size_t i = 0; i < args.processes; i++) {
struct TdoRun *run = &status->runs[i];
if (run->active) {
tdo_run_poll_exit(run, status, arena, output);
tdo_run_poll_exit(&args, run, status, arena, output);
if (run->active) {
double duration = (
(double)(end_time.tv_sec - run->start_time.tv_sec)
+ (double)(end_time.tv_nsec - run->start_time.tv_nsec) * 1e-9
);
double duration = tdo_time_between(end_time, run->start_time);
if (duration > args.time_limit) {
// timeout
if (status->finished > 0) fprintf(output, ",");
tdo_run_report_status(run, arena, output, 0, duration, true);
tdo_run_report_status(&args, status, run, arena, output, 0, duration, true);

kill(run->pid, SIGKILL);
run->active = false;
Expand All @@ -313,8 +303,15 @@ enum TdoError tdo_run_status_init(struct TdoRunStatus *status, struct TdoArena *
.started = 0,
.finished = 0,
.running = 0,
.success = 0,
.exit = 0,
.timeout = 0,
.signal = 0,
.error = 0,
.success_in_a_row = 0,
.fork_failed = false,
.log_setup_failed = false,
.any_failed = false,
};

status->runs = tdo_arena_alloc(arena, sizeof(struct TdoRun), args.processes);
Expand Down
Loading
Loading