Last active
November 5, 2023 01:39
-
-
Save frodo821/8a6e37a74391cd1a428ed349ff52fbc6 to your computer and use it in GitHub Desktop.
Cヘッダファイル1つのみの簡易的なユニットテストライブラリ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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