From a61536f9d34daf94b2824eb2f3098df5f732163f Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Wed, 14 Jan 2026 23:49:24 +0100 Subject: tests: achieve 100% coverage in library code --- .gitignore | 1 + Makefile | 3 ++ ls_queue.h | 10 ++++-- scripts/test_coverage.sh | 30 +++++++++++++++++ tests/tests.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100755 scripts/test_coverage.sh diff --git a/.gitignore b/.gitignore index 80462e0..638ccdc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ tests/tests .cache/ ls_queue.o compile_commands.json +coverage/ diff --git a/Makefile b/Makefile index f145bb2..ed1c393 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# CAUTION: This Makefile builds ONLY the tests. +# To use this library, see ls_test.h or README.md. + tests/tests: ls_queue.o tests/tests.c tests/ls_test.h $(CC) -o $@ ls_queue.o tests/tests.c -Itests -I. diff --git a/ls_queue.h b/ls_queue.h index 0e5fb8f..33b526c 100644 --- a/ls_queue.h +++ b/ls_queue.h @@ -118,9 +118,10 @@ /* Returns 0 if empty, 1 if successful. */ \ int name##_pop(name* q, T* out); -/* DO NOT USE. Use LS_QUEUE_TYPE_INLINE or LS_QUEUE_TYPE_{IMPL,DECL} instead. +/* DO NOT USE THE MACRO BELOW. Use LS_QUEUE_TYPE_INLINE or + * LS_QUEUE_TYPE_{IMPL,DECL} instead. * - * What follows is some ramblings about the implementation. + * What follows are some ramblings about the implementation. * * You might notice that the queue struct has two different names, once the * normal name, e.g. `int_queue`, and once a name with size, `struct @@ -134,6 +135,11 @@ * > In included file: typedef redefinition with different types ('struct * int_queue_18' vs 'struct int_queue_16') (clang * redefinition_different_typedef) + * + * Here you can see that the sizes differ in the declarations: one was declared + * with 16 elements, the other with 18. However, it's really ugly to use those, + * so the typedefs exist, and the functions are named without the size + * specifier, too. */ #define _ls_QUEUE_TYPE_IMPL_DETAIL(T, name, cap, specifier) \ specifier void name##_init(struct name##_##cap* q) { \ diff --git a/scripts/test_coverage.sh b/scripts/test_coverage.sh new file mode 100755 index 0000000..a097db5 --- /dev/null +++ b/scripts/test_coverage.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# This file is mostly AI generated. +set -eu + +# Absolute paths +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$SCRIPT_DIR/.." +COVERAGE_DIR="$ROOT_DIR/coverage" + +# Clean and prepare coverage dir +rm -rf "$COVERAGE_DIR" +mkdir -p "$COVERAGE_DIR" +cd "$COVERAGE_DIR" + +# Build test binary with coverage instrumentation +# Also disable asserts because they do not count for us +gcc -Wall -Wextra -fprofile-arcs -ftest-coverage \ + -DNDEBUG \ + ../ls_queue.h ../tests/tests.c -I../tests -I.. -o tests_cov_tmp + +# Run test binary (generates .gcda files here) +./tests_cov_tmp + +# Generate coverage reports (all output local to coverage/) +gcovr --root .. --object-directory . --exclude-directories tests --output coverage.txt +gcovr --root .. --object-directory . --exclude-directories tests --html --html-details -o coverage.html + +echo "Coverage summary: $COVERAGE_DIR/coverage.txt" +echo "HTML report: $COVERAGE_DIR/coverage.html" diff --git a/tests/tests.c b/tests/tests.c index 5edb0dc..c07c5f3 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -123,4 +123,92 @@ TEST_CASE(queue_circular_behavior) { return 0; } +TEST_CASE(queue_push_pop_full_empty) { + int_queue q; + int val; + int_queue_init(&q); + + // Fill queue + ASSERT_EQ(int_queue_push(&q, 1), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 2), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 3), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 4), 1, "%d"); + // Now full, next push should fail + ASSERT_EQ(int_queue_push(&q, 5), 0, "%d"); + + // Pop all + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + // Now empty, next pop should fail + ASSERT_EQ(int_queue_pop(&q, &val), 0, "%d"); + return 0; +} + +TEST_CASE(queue_wraparound_full_empty) { + int_queue q; + int val; + int_queue_init(&q); + + // Fill and empty several times to force wraparound + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 4; ++j) + ASSERT_EQ(int_queue_push(&q, j), 1, "%d"); + ASSERT_EQ(int_queue_push(&q, 99), 0, "%d"); // full + for (int j = 0; j < 4; ++j) + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(int_queue_pop(&q, &val), 0, "%d"); // empty + } + return 0; +} + +/* Edge-case: fill, pop one, push one, fill again (write wraps, read not at 0) */ +TEST_CASE(queue_fill_wraparound_pointer_equality) { + int_queue q; + int val; + int_queue_init(&q); + + // Fill queue + for (int i = 0; i < 4; ++i) + ASSERT_EQ(int_queue_push(&q, i), 1, "%d"); + // Pop one + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + // Push one (write wraps to 0) + ASSERT_EQ(int_queue_push(&q, 42), 1, "%d"); + // Fill again should fail (full) + ASSERT_EQ(int_queue_push(&q, 99), 0, "%d"); + // Pop all, check order + for (int i = 1; i < 4; ++i) { + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(val, i, "%d"); + } + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(val, 42, "%d"); + // Now empty, next pop should fail + ASSERT_EQ(int_queue_pop(&q, &val), 0, "%d"); + return 0; +} + +/* Edge-case: pop all, push one, pop one, check pointers (read wraps to 0) */ +TEST_CASE(queue_empty_wraparound_pointer_equality) { + int_queue q; + int val; + int_queue_init(&q); + + // Fill and empty queue + for (int i = 0; i < 4; ++i) + ASSERT_EQ(int_queue_push(&q, i), 1, "%d"); + for (int i = 0; i < 4; ++i) + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + // Now empty, push one + ASSERT_EQ(int_queue_push(&q, 77), 1, "%d"); + // Pop one, should be 77 + ASSERT_EQ(int_queue_pop(&q, &val), 1, "%d"); + ASSERT_EQ(val, 77, "%d"); + // Now empty again, next pop should fail + ASSERT_EQ(int_queue_pop(&q, &val), 0, "%d"); + return 0; +} + TEST_MAIN -- cgit