aboutsummaryrefslogtreecommitdiff
path: root/utest
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2023-07-22 03:03:09 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2023-07-22 03:03:09 +0300
commitcb1a40859029f33184355475e51fec95afb79a73 (patch)
tree5aea859d3a3e10ddd058beac9d6734d17979d560 /utest
downloadc-wares-cb1a40859029f33184355475e51fec95afb79a73.tar.gz
c-wares-cb1a40859029f33184355475e51fec95afb79a73.zip
init
Just some C wares; hmap, list, optional, unit tests.
Diffstat (limited to 'utest')
-rw-r--r--utest/Makefile5
-rw-r--r--utest/utest-test.c34
-rw-r--r--utest/utest.h221
3 files changed, 260 insertions, 0 deletions
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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+/*
+ * 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 */