From cb1a40859029f33184355475e51fec95afb79a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Sat, 22 Jul 2023 03:03:09 +0300 Subject: init Just some C wares; hmap, list, optional, unit tests. --- utest/Makefile | 5 ++ utest/utest-test.c | 34 +++++++++ utest/utest.h | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 utest/Makefile create mode 100644 utest/utest-test.c create mode 100644 utest/utest.h (limited to 'utest') diff --git a/utest/Makefile b/utest/Makefile new file mode 100644 index 0000000..0ecb1c5 --- /dev/null +++ b/utest/Makefile @@ -0,0 +1,5 @@ +CC?=gcc +CFLAGS+=-Wall -Werror -Wno-unused -std=gnu11 -Og -g + +all: + $(CC) $(CFLAGS) -o utest-test utest-test.c diff --git a/utest/utest-test.c b/utest/utest-test.c new file mode 100644 index 0000000..591ee32 --- /dev/null +++ b/utest/utest-test.c @@ -0,0 +1,34 @@ +#include "utest.h" + +static int a, b; + +/* This should pass */ +TEST_BEGIN(test_pass) +{ + asserteq(a, 0); + assertneq(a, b); + expect(b > a, "expected b > a"); + TEST_OUT +} +TEST_END + +/* This should fail */ +TEST_BEGIN(test_fail) +{ + expect(0, "oopsie!"); + TEST_OUT +} +TEST_END + +void test_init(void) +{ + a = 0, b = 1; + printf("hello, world!\n\n"); +} + +void test_destroy(void) +{ + printf("\ngood bye!\n"); +} + +RUN_TEST_SUITE(test_init, test_destroy, test_pass, test_fail); diff --git a/utest/utest.h b/utest/utest.h new file mode 100644 index 0000000..218b2b7 --- /dev/null +++ b/utest/utest.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/** + * utest.h - header-only unit testing micro "framework". v1.0.0 + * + * Copyright (c) 2021-2023 Yaroslav de la Peña Smirnov + */ +#ifndef UTEST_H +#define UTEST_H +#include +#include +#include +#include + +/* + * TODO?: test runners that run in different processes in order to not crash the + * whole test unit when a single test crashes. + */ + +/** + * Constants + * ========= + */ + +/** + * Terminal escape codes + * --------------------- + * + * TBLD: bold text. + * TRED: red text. + * TGRN: green text. + * TBLU: blue text. + * TRST: reset colors. + * TDEL: delete current line. + */ +#ifndef NOCOLOR +#define TBLD "\033[1m" +#define TRED "\033[31m" +#define TGRN "\033[32m" +#define TBLU "\033[34m" +#define TRST "\033[0m" +#else +#define TBLD "" +#define TRED "" +#define TGRN "" +#define TBLU "" +#define TRST "" +#endif +#define TDEL "\33[2K\r" + +/** + * Typedefs + * ======== + */ + +typedef bool (*test_fn)(void); +typedef void (*setup_fn)(void); + +/** + * Internal variables + * ================== + */ + +static setup_fn tests_init = NULL; +static setup_fn tests_destroy = NULL; + +/** + * Functions and function-like macros + * ================================== + */ + +/** + * FAIL_TEST - fail the test and print the @reason. + * @reason: a variable list of arguments, where the first argument is the format + * text. + */ +#define FAIL_TEST(reason...) \ + printf(TDEL " [" TBLD TRED "×" TRST "]\t%s: " TBLD TRED "FAILED!\n" TRST, \ + __this_name); \ + printf("\t﹂%s:%d: ", __FILE__, __LINE__); \ + printf(reason); \ + printf("\n"); \ + __ret = false; \ + goto test_out + +/** + * asserteq - assert @a and @b's equality; fail test if not equal. + * @a: argument to compare to b. + * @b: argument to compare to a. + */ +#define asserteq(a, b) \ + ({ \ + if ((a) != (b)) { \ + FAIL_TEST("assertion " TBLD TBLU #a " == " #b TRST " failed"); \ + } \ + }) + +/** + * assertneq - assert @a and @b's inequality; fail test if equal. + * @a: argument to compare to b. + * @b: argument to compare to a. + */ +#define assertneq(a, b) \ + ({ \ + if ((a) == (b)) { \ + FAIL_TEST("assertion " TBLD TBLU #a " != " #b TRST " failed"); \ + } \ + }) + +/** + * expect - fail the test on !@cond and print the reason. + * @cond: condition to test. + * @reason: a variable list of arguments, where the first argument is the format + * text. + */ +#define expect(cond, reason...) \ + ({ \ + if (!(cond)) { \ + FAIL_TEST(reason); \ + } \ + }) + +/** + * TEST_BEGIN - declare test function @name. + * @name: name of test function to declare. + * + * Should be followed by TEST_OUT and TEST_END. + */ +#define TEST_BEGIN(name) \ + int name(void) \ + { \ + const char *__this_name = #name; \ + bool __ret = true; \ + printf(" [⏳]\t%s: " TBLU "running..." TRST, __this_name); \ + fflush(stdout); + +/** + * TEST_OUT - code that should be executed on test exit goes after this. + * This declares a goto label, so that on test failure anything that needs to be + * cleaned up is cleaned up. You need to use this even if there's nothing to be + * cleaned up. + */ +#define TEST_OUT \ +test_out:; + +/** + * TEST_END - close the test scope. + */ +#define TEST_END \ + if (__ret) { \ + printf(TDEL " [" TBLD TGRN "✓" TRST "]\t%s: " TBLD TGRN \ + "PASSED!\n" TRST, \ + __this_name); \ + } \ + return __ret; \ + } + +/** + * __run_tests() - runs tests; for internal use. + */ +static inline int __run_tests(const char *const unit_name, ...) +{ + va_list args; + test_fn test; + size_t passed = 0, total = 0, failed = 0; + + if (tests_init) { + tests_init(); + } + + printf("[***]\t" TBLD "running:" TRST TBLU " %s " TRST "tests\n", + unit_name); + + va_start(args, unit_name); + test = va_arg(args, test_fn); + while (test) { + test() ? passed++ : 0; + total++; + test = va_arg(args, test_fn); + } + va_end(args); + + failed = total - passed; + + printf("[***]\t" TBLD "finished:" TRST TBLU " %s: " TRST "tests: " TBLU + "%lu" TRST ", passed: " TGRN "%lu" TRST ", failed: " TRED "%lu" TRST + "\n", + unit_name, total, passed, failed); + + if (tests_destroy) { + tests_destroy(); + } + + return failed; +} + +/** + * RUN_TEST_SUITE - runs the @tests given as arguments. + * @init_fn: initialization function. + * @destroy_fn: cleanup function. + * @tests...: tests to run. + * + * @init_fn and @destroy_fn are used to initialize and cleanup test-related data + * respectively. + */ +#define RUN_TEST_SUITE(init_fn, destroy_fn, tests...) \ + int main(void) \ + { \ + tests_init = init_fn; \ + tests_destroy = destroy_fn; \ + return __run_tests(__FILE__, tests, NULL); \ + } + +/** + * RUN_TESTS - runs the @tests given as arguments. + * @tests...: tests to run. + * + * Same as RUN_TEST_SUITE, but without initialization and cleanup functions + */ +#define RUN_TESTS(tests...) RUN_TEST_SUITE(NULL, NULL, tests) + +#endif /* UTEST_H */ -- cgit v1.2.3