From 5704de985cff1d40359ecd15211cece0fbe79067 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Fri, 15 Nov 2024 15:12:08 -0700 Subject: Add tests to libmisc --- CMakeLists.txt | 22 ++++++++++- GNUmakefile | 10 ++++- libmisc/CMakeLists.txt | 7 ++++ libmisc/assert.c | 4 +- libmisc/include/libmisc/assert.h | 7 ++-- libmisc/include/libmisc/rand.h | 4 +- libmisc/tests/test.h | 21 ++++++++++ libmisc/tests/test_assert.c | 65 +++++++++++++++++++++++++++++++ libmisc/tests/test_endian.c | 32 ++++++++++++++++ libmisc/tests/test_hash.c | 30 +++++++++++++++ libmisc/tests/test_private.c | 29 ++++++++++++++ libmisc/tests/test_rand.c | 82 ++++++++++++++++++++++++++++++++++++++++ libmisc/tests/test_vcall.c | 74 ++++++++++++++++++++++++++++++++++++ 13 files changed, 377 insertions(+), 10 deletions(-) create mode 100644 libmisc/tests/test.h create mode 100644 libmisc/tests/test_assert.c create mode 100644 libmisc/tests/test_endian.c create mode 100644 libmisc/tests/test_hash.c create mode 100644 libmisc/tests/test_private.c create mode 100644 libmisc/tests/test_rand.c create mode 100644 libmisc/tests/test_vcall.c 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 $(:-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 + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_TESTS_TEST_H_ +#define _LIBMISC_TESTS_TEST_H_ + +#include +#include /* 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 + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include + +#include + +#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 + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include /* for memcmp() */ + +#include + +#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 + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include + +#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 + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include + +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 + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include +#include + +#include + +#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)< + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include +#include + +#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; +} -- cgit v1.2.3-2-g168b