aboutsummaryrefslogtreecommitdiffstats
path: root/tests/inc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/inc')
-rw-r--r--tests/inc/test_utils.h246
1 files changed, 246 insertions, 0 deletions
diff --git a/tests/inc/test_utils.h b/tests/inc/test_utils.h
new file mode 100644
index 0000000..0cb6655
--- /dev/null
+++ b/tests/inc/test_utils.h
@@ -0,0 +1,246 @@
+/**
+ * @file test_utils.h
+ * @brief Minimal test harness for libft unit tests.
+ *
+ * Header-only: each test_*.c translation unit gets its own copy
+ * of the counters and helpers via static linkage.
+ *
+ * Output is compact: only failures are printed individually.
+ * Each section gets a one-line summary, and a totals line is
+ * printed at the end.
+ */
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+/* ── global counters ──────────────────────────────────────────────── */
+
+static int _s_pass = 0;
+static int _s_fail = 0;
+
+static const char *_s_section_name = NULL;
+static int _s_section_pass = 0;
+static int _s_section_fail = 0;
+static int _s_had_sections = 0;
+
+/* volatile: prevent dead-store elimination when the return
+ value of the tested function is never read. */
+static volatile size_t _s_sink;
+static volatile int _s_sink_i;
+
+/* ── sections and assertions ──────────────────────────────────────── */
+
+/** Print the current section's summary line and reset its counters. */
+static void
+_s_section_end (void)
+{
+ if (_s_section_name == NULL)
+ return;
+ if (_s_section_fail > 0)
+ printf (" %-16s %d passed, %d failed\n", _s_section_name, _s_section_pass,
+ _s_section_fail);
+ else
+ printf (" %-16s %d passed\n", _s_section_name, _s_section_pass);
+ _s_section_name = NULL;
+ _s_section_pass = 0;
+ _s_section_fail = 0;
+}
+
+/** Close the previous section (if any) and open a new one. */
+static void
+_s_section (const char *name)
+{
+ _s_section_end ();
+ _s_section_name = name;
+ _s_had_sections = 1;
+}
+
+/** Record a pass (@p ok true) or print FAIL and record a failure. */
+static void
+_s_check (const char *label, int ok)
+{
+ if (ok)
+ {
+ _s_pass++;
+ _s_section_pass++;
+ }
+ else
+ {
+ printf (" FAIL %s\n", label);
+ _s_fail++;
+ _s_section_fail++;
+ }
+}
+
+/** Pass if @p got == @p expected; on failure print both values. */
+static void
+_s_check_eq (const char *label, size_t got, size_t expected)
+{
+ if (got == expected)
+ {
+ _s_pass++;
+ _s_section_pass++;
+ }
+ else
+ {
+ printf (" FAIL %s: got %zu, expected %zu\n", label, got, expected);
+ _s_fail++;
+ _s_section_fail++;
+ }
+}
+
+/** Pass if @p got == @p expected (int variant); print both on failure. */
+static void
+_s_check_eq_int (const char *label, int got, int expected)
+{
+ if (got == expected)
+ {
+ _s_pass++;
+ _s_section_pass++;
+ }
+ else
+ {
+ printf (" FAIL %s: got %d, expected %d\n", label, got, expected);
+ _s_fail++;
+ _s_section_fail++;
+ }
+}
+
+/* ── crash detection ──────────────────────────────────────────────── */
+
+/** Run @p fn in a child process; return 1 if it was killed by a signal. */
+static int
+_s_crashes (void (*fn) (void))
+{
+ pid_t pid;
+ int status;
+
+ pid = fork ();
+ if (pid == 0)
+ {
+ fn ();
+ _exit (0);
+ }
+ waitpid (pid, &status, 0);
+ return (WIFSIGNALED (status));
+}
+
+static void
+_s_check_both_crash (const char *label, void (*ft_fn) (void),
+ void (*libc_fn) (void))
+{
+ int ft_crashed;
+ int libc_crashed;
+
+ ft_crashed = _s_crashes (ft_fn);
+ libc_crashed = _s_crashes (libc_fn);
+ if (ft_crashed == libc_crashed)
+ {
+ _s_pass++;
+ _s_section_pass++;
+ }
+ else
+ {
+ printf (" FAIL %s: ft_crashed=%d libc_crashed=%d\n", label,
+ ft_crashed, libc_crashed);
+ _s_fail++;
+ _s_section_fail++;
+ }
+}
+
+/* ── crash-wrapper macros ─────────────────────────────────────────── */
+
+/* pointer-returning expression → _s_sink */
+#define _S_CRASH(tag, expr) \
+ static void _s_crash_##tag (void) { _s_sink = (size_t)(expr); }
+
+/* int-returning expression → _s_sink_i */
+#define _S_CRASH_I(tag, expr) \
+ static void _s_crash_##tag (void) { _s_sink_i = (expr); }
+
+/* void expression (e.g. ft_bzero) */
+#define _S_CRASH_V(tag, expr) \
+ static void _s_crash_##tag (void) { (expr); }
+
+/* needs a local char buf[N] before the expression */
+#define _S_CRASH_BUF(tag, bufsz, expr) \
+ static void _s_crash_##tag (void) \
+ { \
+ char buf[bufsz]; \
+ (void)buf; \
+ (expr); \
+ }
+
+/* ── int-range comparison helper ─────────────────────────────────── */
+
+/* Compare ft(i) vs libc(i) for i in [-1..255].
+ If normalize is true, compares !!ft(i) == !!libc(i) (for is* funcs).
+ Otherwise exact comparison (for toupper/tolower). */
+static void
+_s_test_int_range (const char *name, int (*ft) (int), int (*libc) (int),
+ int normalize)
+{
+ int i;
+ int ft_r;
+ int libc_r;
+ int ok;
+
+ ok = 1;
+ for (i = -1; i <= 255; i++)
+ {
+ if (normalize)
+ {
+ ft_r = !!ft (i);
+ libc_r = !!libc (i);
+ }
+ else
+ {
+ ft_r = ft (i);
+ libc_r = libc (i);
+ }
+ if (ft_r != libc_r)
+ {
+ char label[64];
+ snprintf (label, sizeof (label), "%s(%d): ft=%d libc=%d", name, i,
+ ft_r, libc_r);
+ _s_check (label, 0);
+ ok = 0;
+ }
+ }
+ if (ok)
+ _s_check (name, 1);
+}
+
+/* ── helpers and output ───────────────────────────────────────────── */
+
+#define _S_RAND_ITERS 50
+
+/** Normalize to -1/0/1 for sign comparison (memcmp, strncmp). */
+static int
+_s_sign (int x)
+{
+ if (x > 0)
+ return (1);
+ if (x < 0)
+ return (-1);
+ return (0);
+}
+
+static void
+_s_print_results (void)
+{
+ _s_section_end ();
+ if (_s_fail > 0)
+ printf (" %d passed, %d failed\n", _s_pass, _s_fail);
+ else if (!_s_had_sections)
+ printf (" %d passed\n", _s_pass);
+}
+
+#endif