Skip to content

Instantly share code, notes, and snippets.

@frodo821
Last active November 5, 2023 01:39
Show Gist options
  • Save frodo821/8a6e37a74391cd1a428ed349ff52fbc6 to your computer and use it in GitHub Desktop.
Save frodo821/8a6e37a74391cd1a428ed349ff52fbc6 to your computer and use it in GitHub Desktop.
Cヘッダファイル1つのみの簡易的なユニットテストライブラリ
#ifndef UTILS_UNITTEST_H_
#define UTILS_UNITTEST_H_
#include <stdio.h>
#if defined(linux) || defined(__linux__)
#include <linux/unistd.h>
#elif defined(unix) || defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
#else
#error "Unsupported platform. This header can only be used on linux-like OS."
#endif
#include <string.h>
#include <ctype.h>
#include <time.h>
#define FORMAT_TIME(start, end, buf) \
do \
{ \
long raw_sec = end.tv_sec - start.tv_sec; \
long raw_nsec = end.tv_nsec - start.tv_nsec; \
raw_sec = raw_nsec < 0 ? raw_sec - 1 : raw_sec; \
raw_nsec = raw_nsec < 0 ? raw_nsec + 1000000000 : raw_nsec; \
int nsec = (int)(raw_nsec % 1000); \
int usec = (int)(raw_nsec / 1000 % 1000); \
int msec = (int)(raw_nsec / 1000000 % 1000); \
int sec = (int)(raw_sec % 60); \
int min = (int)(raw_sec / 60 % 60); \
int hour = (int)(raw_sec / 3600 % 24); \
int day = (int)(raw_sec / 86400); \
int cc = 0; \
if (day > 0) \
{ \
cc += sprintf(buf, "%dd ", day); \
} \
if (hour > 0) \
{ \
cc += sprintf(buf[cc], "%dh ", hour); \
} \
if (min > 0) \
{ \
cc += sprintf(buf[cc], "%dm ", min); \
} \
if (sec > 0) \
{ \
cc += sprintf(buf[cc], "%ds ", sec); \
} \
if (msec > 0) \
{ \
cc += sprintf(buf[cc], "%dms ", msec); \
} \
if (usec > 0) \
{ \
cc += sprintf(buf[cc], "%dus ", usec); \
} \
if (nsec > 0) \
{ \
cc += sprintf(buf[cc], "%dns ", nsec); \
} \
} while (0);
#define TIMEIT_BEGINS(name) \
do \
{ \
struct timespec timeit_gettime_resolution, timeit_gettime_start_time, timeit_gettime_end_time; \
char *__timeit_session_name = name; \
clock_getres(CLOCK_MONOTONIC, &timeit_gettime_resolution); \
fprintf(dupped_stdout, INFO("[timeit begins] ") "%s\n", __timeit_session_name); \
clock_gettime(CLOCK_MONOTONIC, &timeit_gettime_start_time);
#define TIMEIT_ENDS() \
clock_gettime(CLOCK_MONOTONIC, &timeit_gettime_end_time); \
char timeit_formatted[100]; \
FORMAT_TIME(timeit_gettime_start_time, timeit_gettime_end_time, (char *)&timeit_formatted); \
fprintf(dupped_stdout, INFO("[timeit ends] ") "(%s) Time elapsed: %s\n", \
__timeit_session_name, timeit_formatted); \
} \
while (0) \
;
#define FAILURE(msg) "\033[31m" msg "\033[0m"
#define SUCCESS(msg) "\033[32m" msg "\033[0m"
#define INFO(msg) "\033[36m" msg "\033[0m"
#define STDOUT_BUFFER_SIZE BUFSIZ
#define STDOUT_BUFFER __the_stdout_buffer_char_array
#define dupped_stdout __unittest_the_stdout_file
#define STDERR_BUFFER_SIZE BUFSIZ
#define STDERR_BUFFER __the_std_error_buffer_char_array
#define dupped_stderr __unittest_the_stderr_file
#define sanitize_string(str, to, __str_length__) \
do \
{ \
int length = strnlen((str), (__str_length__)); \
char *ptr = ((char *)to); \
for (int i = 0; i < length; i++) \
{ \
switch (str[i]) \
{ \
case '\n': \
*ptr++ = '\\'; \
*ptr++ = 'n'; \
break; \
case '\t': \
*ptr++ = '\\'; \
*ptr++ = 't'; \
break; \
case '\r': \
*ptr++ = '\\'; \
*ptr++ = 'r'; \
break; \
default: \
if (isprint(str[i])) \
{ \
*ptr++ = str[i]; \
} \
else \
{ \
*ptr++ = '\\'; \
*ptr++ = 'x'; \
*ptr++ = "0123456789abcdef"[str[i] & 0x0f]; \
*ptr++ = "0123456789abcdef"[str[i] >> 4]; \
} \
} \
} \
*ptr = '\0'; \
} while (0)
#define clear_buffers() \
do \
{ \
memset(STDOUT_BUFFER, 0, STDOUT_BUFFER_SIZE); \
memset(STDERR_BUFFER, 0, STDERR_BUFFER_SIZE); \
} while (0)
#define __UNITTEST_INTERNAL_CAPTURE_STDOUT() \
FILE *dupped_stdout = fdopen(dup(fileno(stdout)), "a"); \
char STDOUT_BUFFER[STDOUT_BUFFER_SIZE]; \
freopen("/dev/null", "a", stdout); \
setbuf(stdout, STDOUT_BUFFER);
#define __UNITTEST_INTERNAL_CAPTURE_STDERR() \
FILE *dupped_stderr = fdopen(dup(fileno(stderr)), "a"); \
char STDERR_BUFFER[STDERR_BUFFER_SIZE]; \
freopen("/dev/null", "a", stderr); \
setbuf(stderr, STDERR_BUFFER);
#define SETUP_UNITTEST() \
__UNITTEST_INTERNAL_CAPTURE_STDOUT(); \
__UNITTEST_INTERNAL_CAPTURE_STDERR();
#define TEARDOWN_UNITTEST() \
fclose(dupped_stdout); \
fclose(dupped_stderr);
#define TEST_SUITE(name) \
do \
{ \
int test_suite_passed_case_count = 0; \
int test_suite_failed_case_count = 0; \
fprintf(dupped_stdout, "\033[36m[suite begins]\033[0m " name "\n");
#define TEST_CASE(expr, should, value) \
do \
{ \
fprintf(dupped_stdout, " " #expr " " #should " " #value " ... "); \
int equality = (expr) == (value); \
fflush(stdout); \
fflush(stderr); \
fprintf(dupped_stdout, equality ? SUCCESS("OK") : FAILURE("NG")); \
fprintf(dupped_stdout, "\n"); \
if (equality) \
{ \
test_suite_passed_case_count++; \
} \
else \
{ \
test_suite_failed_case_count++; \
} \
clear_buffers(); \
} while (0);
#define END_SUITE() \
fprintf(dupped_stdout, INFO("[suite ends with ") SUCCESS("%d successes") INFO(" and ") FAILURE("%d failures") INFO("]\033[0m \n\n"), test_suite_passed_case_count, test_suite_failed_case_count); \
} \
while (0) \
;
#define ASSERT_SAME_STRING(str, should, expect) \
do \
{ \
char sanitized[BUFSIZ * 3]; \
sanitize_string(str, &sanitized, BUFSIZ); \
fprintf(dupped_stdout, " " #str " (\"%s\") " #should " " #expect " ... ", sanitized); \
int equality = strcmp(str, expect) == 0; \
fflush(stdout); \
fflush(stderr); \
fprintf(dupped_stdout, equality ? SUCCESS("OK") : FAILURE("NG")); \
fprintf(dupped_stdout, "\n"); \
if (equality) \
{ \
test_suite_passed_case_count++; \
} \
else \
{ \
test_suite_failed_case_count++; \
sanitize_string(expect, &sanitized, BUFSIZ); \
fprintf(dupped_stderr, " " FAILURE("Expected: \"%s\"") "\n", sanitized); \
sanitize_string(str, &sanitized, BUFSIZ); \
fprintf(dupped_stderr, " " FAILURE("Actual : \"%s\"") "\n", sanitized); \
} \
clear_buffers(); \
} while (0)
;
#endif // UTILS_UNITTEST_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unittest.h"
int main(void)
{
SETUP_UNITTEST();
TEST_SUITE("adding integers")
{
TEST_CASE(1 + 1, should equal to, 2);
TEST_CASE(1 + 2, should equal to, 3);
TEST_CASE(2 + 1, should equal to, 3);
}
END_SUITE();
TEST_SUITE("printf can be used to write a string to stdout")
{
printf("Hello, World!!\n");
TEST_CASE(strcmp(STDOUT_BUFFER, "Hello, World!!\n"), should be, 0);
}
END_SUITE();
TEST_SUITE("can capture stderr too")
{
fprintf(stderr, "Hello, World!!\n");
TEST_CASE_STRING(STDERR_BUFFER, should be, "Hello, World!!\n");
}
END_SUITE();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment