Lecture 14 Testing Framework
test.c
#include "safe_assert.h"
#include<string.h>
#include<assert.h>
int n = 256;
suite("My Test Suite") {
test("Should compare strings") {
safe_assert(strncmp("foo", "bar", 3) != 0);
}
test("Should work with any char* ?") {
char* null_ptr = NULL;
safe_assert(false);
safe_assert(strncpy(null_ptr, "foo", n));
}
test("I'm a bad test") {
char* s = NULL;
*s = 'a';
}
}
Makefile
.PHONY: all clean
CC_OPTS = -g -Wall -std=c11
ifdef NOCOLOR
CC_OPTS += -D NOCOLOR
endif
all: test
test: test.o
gcc ${CC_OPTS} -o $@ $<
test.o: test.c safe_assert.h
gcc ${CC_OPTS} -o $@ -c $<
clean:
rm -f test *.o *.out
safe_assert.h
// Copyright 2020 Andrew Hu
/* Single header file unit testing framework
* #include "safe_assert.h" at the top of your file
* and _don't_ use a main() function. Instead add
*
* suite("Suite Name") { ... }
*
* Inside of the suite, add various tests inside of
*
* it("Should blah blah blah" { ... }
*
* Use safe_assert() just like assert() from assert.h
* */
#ifndef _SAFE_ASSERT_H_
#define _SAFE_ASSERT_H_
#ifndef NOCOLOR
#define __SA_COLOR_RESET__ "\x1B[0m"
#define __SA_COLOR_RED__ "\x1B[31m" /* Red */
#define __SA_COLOR_GREEN__ "\x1B[32m" /* Green */
#else
#define __SA_COLOR_RESET__ "" /* Make colors a No-op */
#define __SA_COLOR_RED__ ""
#define __SA_COLOR_GREEN__ ""
#endif
#include<sys/types.h>
#include<unistd.h>
#include<wait.h>
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>
pid_t sa_fork_res;
int sa_child_status;
bool any_failed_in_it;
bool any_it_failed = false;
#define SA_SEGFAULT 11
#define safe_assert(cond) sa_fork_res = fork();\
if (sa_fork_res == 0) /*Only run cond in child*/ {\
if (!(cond))\
exit(EXIT_FAILURE);\
else\
exit(EXIT_SUCCESS);\
} else {\
wait(&sa_child_status);\
if (sa_child_status != EXIT_SUCCESS) /*Allow status SIGSEGV for segfault*/ {\
printf(" "__SA_COLOR_RED__"%s"__SA_COLOR_RESET__\
" (%s:%d): ", sa_child_status == SA_SEGFAULT ? "Segfault" : "Assert failed",\
__FILE__, __LINE__);\
printf("`%s'\n", #cond);\
fflush(stdout);\
any_failed_in_it = true;\
continue;\
} else {\
int useless = 0;/*Execute cond*/\
if(cond) { useless++; } else { useless--; }\
}\
}
extern char* suite_name;
#define suite(name) char* suite_name = name;\
void suite_main() /* suite definition follows this macro */
void suite_main();
int main(int argc, char** argv) {
printf("Suite: %s\n", suite_name);
suite_main();
return any_it_failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
int start_it(char* message) {
printf(" %s\n", message);
int res = fork();
if (res == 0) { // Child
return true;
} else { // Parent
int status;
wait(&status);
int failed = status != EXIT_SUCCESS;
printf(" (%s)%s\n", failed ? __SA_COLOR_RED__ "FAILED" __SA_COLOR_RESET__
: __SA_COLOR_GREEN__ "OK" __SA_COLOR_RESET__,
status == SA_SEGFAULT ?
" : "__SA_COLOR_RED__"Segfault outside of safe_assert"__SA_COLOR_RESET__ : "");
return false;
}
}
int finish_it(bool failed) {
if (failed)
exit(EXIT_FAILURE);
else
exit(EXIT_SUCCESS);
}
#define test(description) \
for (bool has_run = false, any_failed_in_it = false;\
has_run < 1 ? (start_it(description)) : finish_it(any_failed_in_it);\
has_run = true)
#endif