summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-11-15 15:12:08 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-11-15 21:40:37 -0700
commit5704de985cff1d40359ecd15211cece0fbe79067 (patch)
tree5c172a6ea91716f4f8023e58d580e4b08fbd7fc1
parentf753128b22b61d4f85a74ba2694b8f9a576fc238 (diff)
Add tests to libmisc
-rw-r--r--CMakeLists.txt22
-rw-r--r--GNUmakefile10
-rw-r--r--libmisc/CMakeLists.txt7
-rw-r--r--libmisc/assert.c4
-rw-r--r--libmisc/include/libmisc/assert.h7
-rw-r--r--libmisc/include/libmisc/rand.h4
-rw-r--r--libmisc/tests/test.h21
-rw-r--r--libmisc/tests/test_assert.c65
-rw-r--r--libmisc/tests/test_endian.c32
-rw-r--r--libmisc/tests/test_hash.c30
-rw-r--r--libmisc/tests/test_private.c29
-rw-r--r--libmisc/tests/test_rand.c82
-rw-r--r--libmisc/tests/test_vcall.c74
13 files changed, 377 insertions, 10 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c9774c6..6e42490 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,9 +70,29 @@ function(add_stack_analysis arg_outfile arg_objlib_target)
)
endfunction()
+include(CTest)
+if (NOT DEFINED ENABLE_TESTS)
+ if (PICO_PLATFORM STREQUAL "host")
+ set(ENABLE_TESTS 1)
+ else()
+ set(ENABLE_TESTS 0)
+ endif()
+endif()
+
+function(add_lib_test arg_libname arg_testname)
+ if (ENABLE_TESTS)
+ add_executable("${arg_testname}" "tests/${arg_testname}.c")
+ target_link_libraries("${arg_testname}" "${arg_libname}")
+ add_test(
+ NAME "${arg_libname}/${arg_testname}"
+ COMMAND valgrind --error-exitcode=2 "./${arg_testname}"
+ )
+ endif()
+endfunction()
+
+add_subdirectory(libmisc)
add_subdirectory(libcr)
add_subdirectory(libcr_ipc)
-add_subdirectory(libmisc)
add_subdirectory(libhw)
add_subdirectory(libdhcp)
add_subdirectory(libusb)
diff --git a/GNUmakefile b/GNUmakefile
index 1e77534..dc94e46 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -58,7 +58,7 @@ generate-clean:
rm -f -- $(generate/files)
.PHONY: generate-clean
-# `build` ######################################################################
+# `build` and `check` ##########################################################
platforms := $(shell sed -nE 's/if *\(PICO_PLATFORM STREQUAL "(.*)"\)/\1/p' cmd/*/CMakeLists.txt)
@@ -72,6 +72,14 @@ $(foreach p,$(platforms),build/$p/build): build/%/build: build/%/Makefile genera
$(MAKE) -C $(<D)
.PHONY: $(foreach p,$(platforms),build/$p/build)
+check: build
+ $(MAKE) -j1 -k $(foreach p,$(platforms),build/$p/check)
+.PHONY: check
+
+$(foreach p,$(platforms),build/$p/check): build/%/check: build/%/Makefile
+ CTEST_OUTPUT_ON_FAILURE=1 $(MAKE) -C $(<D) test
+.PHONY: $(foreach p,$(platforms),build/$p/check)
+
# `lint` and `format` ##########################################################
-include build-aux/sources.mk
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 1f30906..68b6910 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -9,3 +9,10 @@ target_sources(libmisc INTERFACE
assert.c
)
target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
+
+add_lib_test(libmisc test_assert)
+add_lib_test(libmisc test_endian)
+add_lib_test(libmisc test_hash)
+add_lib_test(libmisc test_private)
+add_lib_test(libmisc test_rand)
+add_lib_test(libmisc test_vcall)
diff --git a/libmisc/assert.c b/libmisc/assert.c
index 9b1a0bc..3f4df47 100644
--- a/libmisc/assert.c
+++ b/libmisc/assert.c
@@ -14,8 +14,8 @@
static bool in_fail = false;
-__attribute__((__noreturn__)) void
-__assert_msg_fail(const char *expr,
+__attribute__((noreturn, weak))
+void __assert_msg_fail(const char *expr,
const char *file, unsigned int line, const char *func,
const char *msg) {
if (!in_fail) {
diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h
index 10cedfa..525d7d5 100644
--- a/libmisc/include/libmisc/assert.h
+++ b/libmisc/include/libmisc/assert.h
@@ -11,10 +11,9 @@
# define assert_msg(expr, msg) ((void)0)
#else
# define assert_msg(expr, msg) do { if (!(expr)) __assert_msg_fail(#expr, __FILE__, __LINE__, __func__, msg); } while (0)
-__attribute__((__noreturn__)) void
-__assert_msg_fail(const char *expr,
- const char *file, unsigned int line, const char *func,
- const char *msg);
+void __assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg);
#endif
#define assert(expr) assert_msg(expr, NULL) /* C89, POSIX-2001 */
diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h
index bdb0db9..8072841 100644
--- a/libmisc/include/libmisc/rand.h
+++ b/libmisc/include/libmisc/rand.h
@@ -17,6 +17,7 @@
* `cnt` must not be greater than 1<<63.
*/
static inline uint64_t rand_uint63n(uint64_t cnt) {
+ assert(cnt != 0 && ((cnt-1) & 0x8000000000000000) == 0);
if (cnt <= UINT64_C(1)<<31) {
uint32_t fair_cnt = ((UINT32_C(1)<<31) / cnt) * cnt;
uint32_t rnd;
@@ -38,9 +39,8 @@ static inline uint64_t rand_uint63n(uint64_t cnt) {
rnd = (random() << 62) | (random() << 31) | random();
} while (rnd >= fair_cnt);
return rnd % cnt;
- } else {
- assert_notreached("cnt is out of bounds");
}
+ assert_notreached("cnt is out of bounds");
}
#endif /* _LIBMISC_RAND_H_ */
diff --git a/libmisc/tests/test.h b/libmisc/tests/test.h
new file mode 100644
index 0000000..ea13d3c
--- /dev/null
+++ b/libmisc/tests/test.h
@@ -0,0 +1,21 @@
+/* libmisc/tests/test.h - Common test utilities
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_TESTS_TEST_H_
+#define _LIBMISC_TESTS_TEST_H_
+
+#include <stdio.h>
+#include <stdlib.h> /* for exit() */
+
+#define test_assert(expr) do { \
+ if (!(expr)) { \
+ printf("test failure: %s:%d:%s: %s\n", \
+ __FILE__, __LINE__, __func__, #expr); \
+ exit(1); \
+ } \
+ } while (0)
+
+#endif /* _LIBMISC_TESTS_TEST_H_ */
diff --git a/libmisc/tests/test_assert.c b/libmisc/tests/test_assert.c
new file mode 100644
index 0000000..949b4f9
--- /dev/null
+++ b/libmisc/tests/test_assert.c
@@ -0,0 +1,65 @@
+/* libmisc/tests/test_assert.c - Tests for <libmisc/assert.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+
+#include <libmisc/assert.h>
+
+#include "test.h"
+
+/* Intercept failures *********************************************************/
+
+bool global_failed = false;
+bool global_unreachable = false;
+
+void __assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg) {
+ static bool in_fail = false;
+ if (!in_fail) {
+ in_fail = true;
+ printf("error: %s:%u:%s(): assertion \"%s\" failed%s%s\n",
+ file, line, func,
+ expr,
+ msg ? ": " : "", msg);
+ }
+ global_failed = true;
+}
+
+#define __builtin_unreachable() do { global_unreachable = true; } while (0)
+
+/* Utilities ******************************************************************/
+
+#define test_should_succeed(test) do { \
+ global_failed = false; \
+ test; \
+ test_assert(global_failed == false); \
+ } while (0)
+
+#define test_should_fail(test) do { \
+ global_failed = false; \
+ test; \
+ test_assert(global_failed == true); \
+ } while (0)
+
+/* Actual tests ***************************************************************/
+
+static_assert(sizeof(char) == 1);
+
+int main() {
+ test_should_succeed(assert(true));
+ test_should_fail(assert(false));
+
+ test_should_succeed(assert_msg(true, "foo"));
+ test_should_fail(assert_msg(false, "foo"));
+ test_should_succeed(assert_msg(true, NULL));
+ test_should_fail(assert_msg(false, NULL));
+
+ test_should_fail(assert_notreached(""));
+ test_assert(global_unreachable == true);
+
+ return 0;
+}
diff --git a/libmisc/tests/test_endian.c b/libmisc/tests/test_endian.c
new file mode 100644
index 0000000..d0b547c
--- /dev/null
+++ b/libmisc/tests/test_endian.c
@@ -0,0 +1,32 @@
+/* libmisc/tests/test_endian.c - Tests for <libmisc/endian.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h> /* for memcmp() */
+
+#include <libmisc/endian.h>
+
+#include "test.h"
+
+int main() {
+ uint8_t act[12] = {0};
+ uint16be_encode(&act[0], UINT16_C(0x1234));
+ uint32be_encode(&act[2], UINT32_C(0x56789ABC));
+ uint16le_encode(&act[6], UINT16_C(0x1234));
+ uint32le_encode(&act[8], UINT32_C(0x56789ABC));
+
+ uint8_t exp[12] = { 0x12, 0x34,
+ 0x56, 0x78, 0x9A, 0xBC,
+ 0x34, 0x12,
+ 0xBC, 0x9A, 0x78, 0x56 };
+ test_assert(memcmp(act, exp, 12) == 0);
+
+ test_assert(uint16be_decode(&act[0]) == UINT16_C(0x1234));
+ test_assert(uint32be_decode(&act[2]) == UINT32_C(0x56789ABC));
+ test_assert(uint16le_decode(&act[6]) == UINT16_C(0x1234));
+ test_assert(uint32le_decode(&act[8]) == UINT32_C(0x56789ABC));
+
+ return 0;
+}
diff --git a/libmisc/tests/test_hash.c b/libmisc/tests/test_hash.c
new file mode 100644
index 0000000..c1af385
--- /dev/null
+++ b/libmisc/tests/test_hash.c
@@ -0,0 +1,30 @@
+/* libmisc/tests/test_hash.c - Tests for <libmisc/hash.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/hash.h>
+
+#include "test.h"
+
+int main() {
+ test_assert(hash("hello", sizeof("hello")) == hash("hello", sizeof("hello")));
+ test_assert(hash("hello", sizeof("hello")) != hash("Hello", sizeof("Hello")));
+ int one = 1;
+ int two = 2;
+ test_assert(hash(&one, sizeof(int)) != hash("hello", sizeof("hello")));
+ test_assert(hash(&one, sizeof(int)) == hash(&one, sizeof(int)));
+ test_assert(hash(&one, sizeof(int)) != hash(&two, sizeof(int)));
+
+ hash_t myhash;
+ hash_init(&myhash);
+ hash_write(&myhash, "hello", 5);
+ test_assert(myhash == hash("hello", 5));
+ hash_t myhash_a = myhash;
+ hash_write(&myhash, "world", 5);
+ test_assert(myhash != myhash_a);
+ test_assert(myhash == hash("helloworld", 10));
+
+ return 0;
+}
diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c
new file mode 100644
index 0000000..7aaf1ee
--- /dev/null
+++ b/libmisc/tests/test_private.c
@@ -0,0 +1,29 @@
+/* libmisc/tests/test_private.c - Tests for <libmisc/private.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/private.h>
+
+struct a {
+ int foo;
+ BEGIN_PRIVATE(A)
+ int bar;
+ END_PRIVATE(A)
+};
+
+#define IMPLEMENTATION_FOR_B YES
+
+struct b {
+ int foo;
+ BEGIN_PRIVATE(B)
+ int bar;
+ END_PRIVATE(B)
+};
+
+int main() {
+ struct b obj;
+ obj.bar = 0;
+ return obj.bar;
+}
diff --git a/libmisc/tests/test_rand.c b/libmisc/tests/test_rand.c
new file mode 100644
index 0000000..fff1b27
--- /dev/null
+++ b/libmisc/tests/test_rand.c
@@ -0,0 +1,82 @@
+/* libmisc/tests/test_rand.c - Tests for <libmisc/rand.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <setjmp.h>
+
+#include <libmisc/rand.h>
+
+#include "test.h"
+
+/* Intercept failures *********************************************************/
+
+jmp_buf *__catch;
+
+void __assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg) {
+ static bool in_fail = false;
+ if (__catch)
+ longjmp(*__catch, 1);
+ if (!in_fail) {
+ in_fail = true;
+ printf("error: %s:%u:%s(): assertion \"%s\" failed%s%s\n",
+ file, line, func,
+ expr,
+ msg ? ": " : "", msg);
+ }
+ abort();
+}
+
+#define should_abort(cmd) do { \
+ jmp_buf *old_catch = __catch; \
+ jmp_buf env; \
+ __catch = &env; \
+ if (!setjmp(env)) { \
+ cmd; \
+ __catch = old_catch; \
+ test_assert(false); \
+ } else { \
+ __catch = old_catch; \
+ } \
+ } while (0);
+
+/* Actual tests ***************************************************************/
+
+#define ROUNDS 4096
+#define MAX_SEE_ALL 128
+
+static void test_n(uint64_t cnt) {
+ if (cnt == 0 || cnt > UINT64_C(1)<<63) {
+ should_abort(rand_uint63n(cnt));
+ } else {
+ double sum = 0;
+ bool seen[MAX_SEE_ALL] = {0};
+ for (int i = 0; i < ROUNDS; i++) {
+ uint64_t val = rand_uint63n(cnt);
+ sum += ((double)val)/(cnt-1);
+ test_assert(val < cnt);
+ if (cnt < MAX_SEE_ALL)
+ seen[val] = true;
+ }
+ if (cnt > 1) {
+ test_assert(sum/ROUNDS > 0.45);
+ test_assert(sum/ROUNDS < 0.55);
+ }
+ if (cnt < MAX_SEE_ALL) {
+ for (uint64_t i = 0; i < cnt; i++)
+ test_assert(seen[i]);
+ }
+ }
+}
+
+int main() {
+ for (uint8_t i = 0; i < 64; i++)
+ test_n(UINT64_C(1)<<i);
+ for (uint64_t j = 0; j < MAX_SEE_ALL; j++)
+ test_n(j);
+ return 0;
+}
diff --git a/libmisc/tests/test_vcall.c b/libmisc/tests/test_vcall.c
new file mode 100644
index 0000000..f36fc4b
--- /dev/null
+++ b/libmisc/tests/test_vcall.c
@@ -0,0 +1,74 @@
+/* libmisc/tests/test_vcall.c - Tests for <libmisc/vcall.h>
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libmisc/assert.h>
+#include <libmisc/vcall.h>
+
+#include "test.h"
+
+/******************************************************************************/
+
+struct frobber_vtable;
+
+typedef struct {
+ struct frobber_vtable *vtable;
+} implements_frobber;
+
+struct frobber_vtable {
+ int (*frob)(implements_frobber *);
+ int (*frob1)(implements_frobber *, int);
+ void (*frob0)(implements_frobber *);
+};
+
+/******************************************************************************/
+
+struct myclass {
+ int a;
+ implements_frobber;
+};
+static_assert(offsetof(struct myclass, implements_frobber) != 0);
+
+static int myclass_frob(implements_frobber *_self) {
+ struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
+ test_assert(self);
+ test_assert((void*)self != (void*)_self);
+ return self->a;
+}
+
+static int myclass_frob1(implements_frobber *_self, int arg) {
+ struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
+ test_assert(self);
+ test_assert((void*)self != (void*)_self);
+ return arg;
+}
+
+static void myclass_frob0(implements_frobber *_self) {
+ struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
+ test_assert(self);
+ test_assert((void*)self != (void*)_self);
+}
+
+struct frobber_vtable myclass_vtable = {
+ .frob = myclass_frob,
+ .frob1 = myclass_frob1,
+ .frob0 = myclass_frob0,
+};
+
+/******************************************************************************/
+
+#define MAGIC1 909837
+#define MAGIC2 657441
+
+int main() {
+ struct myclass obj = {
+ .implements_frobber = { .vtable = &myclass_vtable },
+ .a = MAGIC1,
+ };
+ test_assert(VCALL(&obj, frob) == MAGIC1);
+ test_assert(VCALL(&obj, frob1, MAGIC2) == MAGIC2);
+ VCALL(&obj, frob0);
+ return 0;
+}