Skip to content

Instantly share code, notes, and snippets.

@MichaelSnowden
Created August 9, 2020 06:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MichaelSnowden/b75899180ec5c2e1387a2ae8b4e705c4 to your computer and use it in GitHub Desktop.
Save MichaelSnowden/b75899180ec5c2e1387a2ae8b4e705c4 to your computer and use it in GitHub Desktop.
C Test Runner
#include <stdlib.h>
#include <zconf.h>
#include <stdio.h>
#include <fcntl.h>
#include <memory.h>
#include <errno.h>
#include <assert.h>
#include "test_runner.h"
int runTests(Test *tests, int numTests) {
assert(numTests > 0);
int maxNameLength = 0;
for (int i = 0; i < numTests; ++i) {
int length = (int) strlen(tests[i].name);
if (length > maxNameLength) {
maxNameLength = length;
}
}
for (int i = 0; i < numTests; ++i) {
Test *test = tests + i;
int stderrPipe[2];
assert(pipe(stderrPipe) == 0);
int stdoutPipe[2];
assert(pipe(stdoutPipe) == 0);
pid_t pid = fork();
if (pid == 0) {
assert(dup2(stderrPipe[1], STDERR_FILENO) != -1);
assert(dup2(stdoutPipe[1], STDOUT_FILENO) != -1);
// this might exit early, but if, instead, it returns, just use that as the exit code
int testStatus = test->run();
exit(testStatus);
}
const char *name = test->name;
printf("%*s: ", maxNameLength, name);
fflush(stdout);
int status;
wait:
while (waitpid(pid, &status, 0) != pid) {
if (errno == EINTR) {
goto wait;
}
perror("waitpid");
return -1;
}
if (status == 0) {
printf("\033[1;32m" "pass\n" "\033[0m");
continue;
}
ssize_t r;
char buffer[1024];
while ((r = read(stderrPipe[0], buffer, sizeof(buffer) - 1)) < 0 && errno == EINTR) {}
if (r < 0) {
perror("read");
return -1;
}
if (r == 0) {
printf("\033[1;34m" "[no output from test]\n" "\033[0m");
continue;
}
buffer[r] = '\0';
// remove trailing newlines
while (r > 0 && buffer[r - 1] == '\n') {
buffer[r - 1] = '\0';
}
printf("\033[1:31m" "%s\n" "\033[0m", buffer);
}
return 0;
}
#ifndef TEST_RUNNER_H
#define TEST_RUNNER_H
/*
* A lot of tests in C have the same signature: int (*run)().
* This test runner will iterate through a list of such functions, executing each one.
* It runs each test in a subprocess, so that the test is allowed to fuck up in any way imaginable,
* but the test runner continues chugging along.
* The test runner also redirects stdout and stderr in the tests so that they are ignored.
* However, when a test fails, a snippet of stderr from the failed test will be displayed.
*/
typedef struct {
const char *name;
int (*run)();
} Test;
int runTests(Test *test, int numTests);
#endif
#include <printf.h>
#include "test_runner_test.h"
#include "test_runner.h"
int test1() {
printf("log1\n");
fprintf(stderr, "err1\n");
return -1;
}
int test2() {
printf("log2\n");
fprintf(stderr, "err2\n");
return 0;
}
int test3() {
printf("log3\n");
fprintf(stderr, "err3\n");
return -1;
}
int testTestRunner() {
Test tests[] = {
{"test1_jfw0", test1},
{"test2_fjsj29v[zh33gf9sd8", test2},
{"test3_hba03hfaz", test3},
};
int numTests = sizeof(tests) / sizeof(*tests);
return runTests(tests, numTests);
}
#ifndef TEST_RUNNER_TEST_H
#define TEST_RUNNER_TEST_H
int testTestRunner();
#endif
#include "test_runner_test.h"
int main() {
return testTestRunner();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment