aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile3
-rw-r--r--ls_queue.h10
-rwxr-xr-xscripts/test_coverage.sh30
-rw-r--r--tests/tests.c88
5 files changed, 130 insertions, 2 deletions
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