diff options
Diffstat (limited to 'libmisc')
-rw-r--r-- | libmisc/CMakeLists.txt | 21 | ||||
-rw-r--r-- | libmisc/assert.c | 30 | ||||
l--------- | libmisc/include/assert.h | 1 | ||||
-rw-r--r-- | libmisc/include/libmisc/_intercept.h | 27 | ||||
-rw-r--r-- | libmisc/include/libmisc/assert.h | 24 | ||||
-rw-r--r-- | libmisc/include/libmisc/endian.h | 84 | ||||
-rw-r--r-- | libmisc/include/libmisc/linkedlist.h | 46 | ||||
-rw-r--r-- | libmisc/include/libmisc/log.h | 34 | ||||
-rw-r--r-- | libmisc/include/libmisc/macro.h | 107 | ||||
-rw-r--r-- | libmisc/include/libmisc/private.h | 38 | ||||
-rw-r--r-- | libmisc/include/libmisc/rand.h | 46 | ||||
-rw-r--r-- | libmisc/include/libmisc/vcall.h | 27 | ||||
-rw-r--r-- | libmisc/intercept.c | 34 | ||||
-rw-r--r-- | libmisc/linkedlist.c | 62 | ||||
-rw-r--r-- | libmisc/log.c | 273 | ||||
-rw-r--r-- | libmisc/tests/test.h | 21 | ||||
-rw-r--r-- | libmisc/tests/test_assert.c | 94 | ||||
-rw-r--r-- | libmisc/tests/test_assert_min.c | 17 | ||||
-rw-r--r-- | libmisc/tests/test_endian.c | 41 | ||||
-rw-r--r-- | libmisc/tests/test_hash.c | 30 | ||||
-rw-r--r-- | libmisc/tests/test_log.c | 70 | ||||
-rw-r--r-- | libmisc/tests/test_macro.c | 54 | ||||
-rw-r--r-- | libmisc/tests/test_private.c | 29 | ||||
-rw-r--r-- | libmisc/tests/test_rand.c | 79 |
24 files changed, 1228 insertions, 61 deletions
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 0c5c019..c80e060 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -1,8 +1,23 @@ -# libmisc/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions +# libmisc/CMakeLists.txt - Low-level C programming utilities; sort of an augmented "libc" # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libmisc INTERFACE) -target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_sources(libmisc INTERFACE + assert.c + intercept.c + linkedlist.c + log.c +) target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") + +add_lib_test(libmisc test_assert) +add_lib_test(libmisc test_assert_min) +add_lib_test(libmisc test_endian) +add_lib_test(libmisc test_hash) +add_lib_test(libmisc test_log) +add_lib_test(libmisc test_macro) +add_lib_test(libmisc test_private) +add_lib_test(libmisc test_rand) diff --git a/libmisc/assert.c b/libmisc/assert.c new file mode 100644 index 0000000..fdd8154 --- /dev/null +++ b/libmisc/assert.c @@ -0,0 +1,30 @@ +/* libmisc/assert.c - More assertions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdbool.h> /* for bool, true, false */ + +#define LOG_NAME ASSERT +#include <libmisc/log.h> /* for errorf() */ + +#include <libmisc/assert.h> + +#ifndef NDEBUG +#define __lm_printf __lm_light_printf +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; + errorf("%s:%u:%s(): assertion \"%s\" failed%s%s", + file, line, func, + expr, + msg ? ": " : "", msg ?: ""); + in_fail = false; + } + __lm_abort(); +} +#endif diff --git a/libmisc/include/assert.h b/libmisc/include/assert.h new file mode 120000 index 0000000..8473e36 --- /dev/null +++ b/libmisc/include/assert.h @@ -0,0 +1 @@ +libmisc/assert.h
\ No newline at end of file diff --git a/libmisc/include/libmisc/_intercept.h b/libmisc/include/libmisc/_intercept.h new file mode 100644 index 0000000..a264144 --- /dev/null +++ b/libmisc/include/libmisc/_intercept.h @@ -0,0 +1,27 @@ +/* libmisc/_intercept.h - Interceptable ("weak") functions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC__INTERCEPT_H_ +#define _LIBMISC__INTERCEPT_H_ + +/* pico-sdk/newlib define these to be [[gnu:weak]] already, but + * depending on optimization options glibc might not, and GCC might + * assume it knows what they do and optimize them out. So define our + * own `__lm_` wrappers that GCC/glibc won't interfere with. + */ + +[[gnu::format(printf, 1, 2)]] +int __lm_printf(const char *format, ...); + +[[noreturn]] void __lm_abort(void); + +/* __lm_light_printf is expected to have less stack use than regular + * __lm_printf, if possible. */ + +[[gnu::format(printf, 1, 2)]] +int __lm_light_printf(const char *format, ...); + +#endif /* _LIBMISC__INTERCEPT_H_ */ diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h new file mode 100644 index 0000000..8cf0735 --- /dev/null +++ b/libmisc/include/libmisc/assert.h @@ -0,0 +1,24 @@ +/* libmisc/assert.h - More assertions + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_ASSERT_H_ +#define _LIBMISC_ASSERT_H_ + +#ifdef NDEBUG +# define __assert_msg(expr, expr_str, msg) ((void)0) +#else +# define __assert_msg(expr, expr_str, msg) do { if (!(expr)) __assert_msg_fail(expr_str, __FILE__, __LINE__, __func__, msg); } while (0) +[[noreturn]] void __assert_msg_fail(const char *expr, + const char *file, unsigned int line, const char *func, + const char *msg); +#endif + +#define assert_msg(expr, msg) __assert_msg(expr, #expr, msg) /* libmisc */ +#define assert(expr) __assert_msg(expr, #expr, 0) /* C89, POSIX-2001 */ +#define assert_notreached(msg) do { __assert_msg(0, "notreached", msg); __builtin_unreachable(); } while (0) /* libmisc */ +#define static_assert _Static_assert /* C11 */ + +#endif /* _LIBMISC_ASSERT_H_ */ diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h index 24d7d42..75240fe 100644 --- a/libmisc/include/libmisc/endian.h +++ b/libmisc/include/libmisc/endian.h @@ -1,15 +1,17 @@ /* libmisc/endian.h - Endian-conversion helpers * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ #ifndef _LIBMISC_ENDIAN_H_ #define _LIBMISC_ENDIAN_H_ -#include <assert.h> +#include <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t */ +#include <libmisc/assert.h> + /* Big endian *****************************************************************/ typedef struct { @@ -70,6 +72,45 @@ static inline uint32_t uint32be_unmarshal(uint32be_t in) { return uint32be_decode(in.octets); } +typedef struct { + uint8_t octets[8]; +} uint64be_t; +static_assert(sizeof(uint64be_t) == 8); + +static inline size_t uint64be_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 56) & 0xFF); + out[1] = (uint8_t)((in >> 48) & 0xFF); + out[2] = (uint8_t)((in >> 40) & 0xFF); + out[3] = (uint8_t)((in >> 32) & 0xFF); + out[4] = (uint8_t)((in >> 24) & 0xFF); + out[5] = (uint8_t)((in >> 16) & 0xFF); + out[6] = (uint8_t)((in >> 8) & 0xFF); + out[7] = (uint8_t)((in >> 0) & 0xFF); + return 8; +} + +static inline uint64_t uint64be_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 56) + | (((uint64_t)(in[1])) << 48) + | (((uint64_t)(in[2])) << 40) + | (((uint64_t)(in[3])) << 32) + | (((uint64_t)(in[4])) << 24) + | (((uint64_t)(in[5])) << 16) + | (((uint64_t)(in[6])) << 8) + | (((uint64_t)(in[7])) << 0) + ; +} + +static inline uint64be_t uint64be_marshal(uint64_t in) { + uint64be_t out; + uint64be_encode(out.octets, in); + return out; +} + +static inline uint64_t uint64be_unmarshal(uint64be_t in) { + return uint64be_decode(in.octets); +} + /* Little endian **************************************************************/ typedef struct { @@ -130,4 +171,43 @@ static inline uint32_t uint32le_unmarshal(uint32le_t in) { return uint32le_decode(in.octets); } +typedef struct { + uint8_t octets[8]; +} uint64le_t; +static_assert(sizeof(uint64le_t) == 8); + +static inline size_t uint64le_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + out[2] = (uint8_t)((in >> 16) & 0xFF); + out[3] = (uint8_t)((in >> 24) & 0xFF); + out[4] = (uint8_t)((in >> 32) & 0xFF); + out[5] = (uint8_t)((in >> 40) & 0xFF); + out[6] = (uint8_t)((in >> 48) & 0xFF); + out[7] = (uint8_t)((in >> 56) & 0xFF); + return 8; +} + +static inline uint64_t uint64le_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 0) + | (((uint64_t)(in[1])) << 8) + | (((uint64_t)(in[2])) << 16) + | (((uint64_t)(in[3])) << 24) + | (((uint64_t)(in[4])) << 32) + | (((uint64_t)(in[5])) << 40) + | (((uint64_t)(in[6])) << 48) + | (((uint64_t)(in[7])) << 56) + ; +} + +static inline uint64le_t uint64le_marshal(uint64_t in) { + uint64le_t out; + uint64le_encode(out.octets, in); + return out; +} + +static inline uint64_t uint64le_unmarshal(uint64le_t in) { + return uint64le_decode(in.octets); +} + #endif /* _LIBMISC_ENDIAN_H_ */ diff --git a/libmisc/include/libmisc/linkedlist.h b/libmisc/include/libmisc/linkedlist.h new file mode 100644 index 0000000..8adef66 --- /dev/null +++ b/libmisc/include/libmisc/linkedlist.h @@ -0,0 +1,46 @@ +/* libmisc/linkedlist.h - Singly- and doubly- linked lists + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_LINKEDLIST_H_ +#define _LIBMISC_LINKEDLIST_H_ + +#include <libmisc/assert.h> +#include <libmisc/macro.h> + +/* singly linked list *********************************************************/ + +typedef struct _lm_sll_node { + struct _lm_sll_node *rear; +} lm_sll_node; + +typedef struct { + lm_sll_node *front, *rear; +} lm_sll_root; + +#define lm_sll_node_cast(node_typ, node_ptr) \ + LM_CAST_FIELD_TO_STRUCT(node_typ, lm_sll_node, node_ptr) + +void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node); +void lm_sll_pop_from_front(lm_sll_root *root); + +/* doubly linked list *********************************************************/ + +typedef struct _lm_dll_node { + struct _lm_dll_node *front, *rear; +} lm_dll_node; + +typedef struct { + lm_dll_node *front, *rear; +} lm_dll_root; + +#define lm_dll_node_cast(node_typ, node_ptr) \ + LM_CAST_FIELD_TO_STRUCT(node_typ, lm_dll_node, node_ptr) + +void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node); +void lm_dll_remove(lm_dll_root *root, lm_dll_node *node); +void lm_dll_pop_from_front(lm_dll_root *root); + +#endif /* _LIBMISC_LINKEDLIST_H_ */ diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h new file mode 100644 index 0000000..79c0ab6 --- /dev/null +++ b/libmisc/include/libmisc/log.h @@ -0,0 +1,34 @@ +/* libmisc/log.h - stdio logging + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_LOG_H_ +#define _LIBMISC_LOG_H_ + +#include <stdint.h> /* for uint8_t */ + +#include <libmisc/macro.h> +#include <libmisc/_intercept.h> + +#ifdef NDEBUG + #define _LOG_NDEBUG 1 +#else + #define _LOG_NDEBUG 0 +#endif + +const char *const_byte_str(uint8_t b); + +#define n_errorf(nam, fmt, ...) do { __lm_printf("error: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define n_infof(nam, fmt, ...) do { __lm_printf("info : " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) +#define n_debugf(nam, fmt, ...) do { if (LM_CAT3_(CONFIG_, nam, _DEBUG) && !_LOG_NDEBUG) \ + __lm_printf("debug: " LM_STR_(nam) ": " fmt "\n" __VA_OPT__(,) __VA_ARGS__); } while (0) + +#endif /* _LIBMISC_LOG_H_ */ + +#if defined(LOG_NAME) && !defined(errorf) +#define errorf(fmt, ...) n_errorf(LOG_NAME, fmt, __VA_ARGS__) +#define infof(fmt, ...) n_infof(LOG_NAME, fmt, __VA_ARGS__) +#define debugf(fmt, ...) n_debugf(LOG_NAME, fmt, __VA_ARGS__) +#endif diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h new file mode 100644 index 0000000..6cb15fb --- /dev/null +++ b/libmisc/include/libmisc/macro.h @@ -0,0 +1,107 @@ +/* libmisc/macro.h - Useful C preprocessor macros + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_MACRO_H_ +#define _LIBMISC_MACRO_H_ + +/* for function definitions */ + +#define LM_UNUSED(argname) +#define LM_ALWAYS_INLINE [[gnu::always_inline]] inline +#define LM_NEVER_INLINE [[gnu::noinline]] +#define LM_FLATTEN [[gnu::flatten]] + +/* types */ + +#define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) + +#define LM_CAST_FIELD_TO_STRUCT(STRUCT_TYP, FIELD_NAME, PTR_TO_FIELD) ({ \ + /* The _fptr assignment is to get the compiler to do type checking. */ \ + typeof(((STRUCT_TYP *)NULL)->FIELD_NAME) *_fptr = (PTR_TO_FIELD); \ + _fptr \ + ? ((STRUCT_TYP*)( ((void*)_fptr) - offsetof(STRUCT_TYP, FIELD_NAME))) \ + : NULL; \ +}) + +/* numeric */ + +#define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */ +#define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */ +#define LM_ROUND_DOWN(n, d) ( ((n)/(d)) * (d) ) /** Return `n` rounded down to the nearest multiple of `d` */ +#define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1) /** Return the lowest power of 2 that is > x */ +#define LM_FLOORLOG2(x) ((sizeof(unsigned long long)*8)-__builtin_clzll(x)-1) /** Return floor(log_2(x) */ + +/* strings */ + +#define LM_STR(x) #x +#define LM_STR_(x) LM_STR(x) + +/* token pasting */ + +#define LM_CAT2(a, b) a ## b +#define LM_CAT3(a, b, c) a ## b ## c + +#define LM_CAT2_(a, b) LM_CAT2(a, b) +#define LM_CAT3_(a, b, c) LM_CAT3(a, b, c) + +/* macro arguments */ + +#define LM_FIRST(a, ...) a +#define LM_SECOND(a, b, ...) b +#define LM_EAT(...) +#define LM_EXPAND(...) __VA_ARGS__ + +/* conditionals */ + +#define LM_T xxTxx +#define LM_F xxFxx + +#define LM_SENTINEL() bogus, LM_T /* a magic sentinel value */ +#define LM_IS_SENTINEL(...) LM_SECOND(__VA_ARGS__, LM_F) + +#define LM_IF(cond) LM_CAT2(_LM_IF__, cond) /* LM_IF(cond)(then)(else) */ +#define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT +#define _LM_IF__xxFxx(...) LM_EXPAND + +/* tuples */ + +#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) +#define _LM_IS_TUPLE(...) LM_SENTINEL() + +/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */ +#define _LM_TUPLES_COMMA(tuple...) (tuple), +#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples) +#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples)) +#define LM_TUPLES_TAIL(tuples) LM_EAT tuples + +/* iteration */ + +/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */ +#define LM_FOREACH_TUPLE(tuples, func, ...) \ + _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__)) +#define _LM_FOREACH_TUPLE(tuples, func, ...) \ + LM_IF(LM_TUPLES_NONEMPTY(tuples))( \ + _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \ + _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \ + )() +#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE + +#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() + +#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */ +#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__)) +#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__)) +#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__)) +#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__)) +#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__)) +#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__)) +#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__)) +#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__)) +#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__)) +#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__)) +#define _LM_EVAL__1(...) __VA_ARGS__ + +#endif /* _LIBMISC_MACRO_H_ */ diff --git a/libmisc/include/libmisc/private.h b/libmisc/include/libmisc/private.h index 0eb8c42..5518d1f 100644 --- a/libmisc/include/libmisc/private.h +++ b/libmisc/include/libmisc/private.h @@ -1,37 +1,17 @@ /* libmisc/private.h - Utilities to hide struct fields * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#ifndef _LIBMISC_CPPUTIL_H_ -#define _LIBMISC_CPPUTIL_H_ +#ifndef _LIBMISC_PRIVATE_H_ +#define _LIBMISC_PRIVATE_H_ -/* primitive utilities */ +#include <libmisc/macro.h> -#define _SECOND(a, b, ...) b +#define YES LM_SENTINEL() +#define IS_IMPLEMENTATION_FOR(name) LM_IS_SENTINEL(IMPLEMENTATION_FOR_##name) +#define BEGIN_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))()(struct {) struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__) +#define END_PRIVATE(name) LM_IF(IS_IMPLEMENTATION_FOR(name))(struct {} LM_CAT2_(_PRIVATE_FORCE_SEMICOLON_, __COUNTER__))(} LM_CAT2_(_PRIVATE_, __COUNTER__)) -#define _CAT(a, b) a ## b -#define _CAT2(a, b) _CAT(a, b) -#define _EAT(...) -#define _EXPAND(...) __VA_ARGS__ - -/* conditionals */ - -#define _T xxTxx -#define _F xxFxx - -#define _SENTINEL() bogus, _T /* a magic sentinel value */ -#define _IS_SENTINEL(...) _SECOND(__VA_ARGS__, _F) - -#define _IF(cond) _CAT(_IF__, cond) /* _IF(cond)(then)(else) */ -#define _IF__xxTxx(...) __VA_ARGS__ _EAT -#define _IF__xxFxx(...) _EXPAND - -/* high-level functionality */ - -#define YES _SENTINEL() -#define BEGIN_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(struct {) -#define END_PRIVATE(name) _IF(_IS_SENTINEL(IMPLEMENTATION_FOR_##name))()(} _CAT2(_HIDDEN_, __COUNTER__);) - -#endif /* _LIBMISC_CPPUTIL_H_ */ +#endif /* _LIBMISC_PRIVATE_H_ */ diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h new file mode 100644 index 0000000..7ef238b --- /dev/null +++ b/libmisc/include/libmisc/rand.h @@ -0,0 +1,46 @@ +/* libmisc/rand.h - Non-crytpographic random-number utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_RAND_H_ +#define _LIBMISC_RAND_H_ + +#include <stdint.h> /* for uint{n}_t, UINT{n}_C() */ +#include <stdlib.h> /* for random() */ + +#include <libmisc/assert.h> + +/** + * Return a psuedo-random number in the half-open interval [0,cnt). + * `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; + do { + rnd = random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<62) { + uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (((uint64_t)random()) << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<63) { + uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } + assert_notreached("cnt is out of bounds"); +} + +#endif /* _LIBMISC_RAND_H_ */ diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h deleted file mode 100644 index ea9402e..0000000 --- a/libmisc/include/libmisc/vcall.h +++ /dev/null @@ -1,27 +0,0 @@ -/* libmisc/vcall.h - A simple Go-ish object system built on GCC -fplan9-extensions - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBMISC_VCALL_H_ -#define _LIBMISC_VCALL_H_ - -#include <assert.h> /* for assert() and static_assert() */ -#include <stddef.h> /* for offsetof() */ - -#define VCALL(o, m, ...) \ - ({ \ - assert(o); \ - (o)->vtable->m(o __VA_OPT__(,) __VA_ARGS__); \ - }) - -#define VCALL_SELF(obj_typ, iface_typ, iface_ptr) \ - ({ \ - static_assert(_Generic(iface_ptr, iface_typ *: 1, default: 0), \ - "typeof("#iface_ptr") != "#iface_typ); \ - assert(iface_ptr); \ - ((obj_typ*)(((void*)iface_ptr)-offsetof(obj_typ,iface_typ))); \ - }) - -#endif /* _LIBMISC_VCALL_H_ */ diff --git a/libmisc/intercept.c b/libmisc/intercept.c new file mode 100644 index 0000000..85a3801 --- /dev/null +++ b/libmisc/intercept.c @@ -0,0 +1,34 @@ +/* libmisc/intercept.c - Interceptable ("weak") functions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdio.h> /* for vprintf() */ +#include <stdlib.h> /* for abort() */ + +#include <libmisc/_intercept.h> + +[[gnu::weak]] +int __lm_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vprintf(format, va); + va_end(va); + return ret; +} + +[[gnu::weak]] +int __lm_light_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vprintf(format, va); + va_end(va); + return ret; +} + +[[gnu::weak]] +void __lm_abort(void) { + abort(); +} diff --git a/libmisc/linkedlist.c b/libmisc/linkedlist.c new file mode 100644 index 0000000..5fe0977 --- /dev/null +++ b/libmisc/linkedlist.c @@ -0,0 +1,62 @@ +/* libmisc/linkedlist.c - Singly- and doubly- linked lists + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stddef.h> /* for NULL */ + +#include <libmisc/linkedlist.h> + +/* singly linked list *********************************************************/ + +void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node) { + assert(root); + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void lm_sll_pop_from_front(lm_sll_root *root) { + assert(root); + assert(root->front); + root->front = root->front->rear; + if (!root->front) + root->rear = NULL; +} + +/* doubly linked list *********************************************************/ + +void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node) { + assert(root); + assert(node); + node->front = root->rear; + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void lm_dll_remove(lm_dll_root *root, lm_dll_node *node) { + assert(root); + assert(node); + if (node->front) + node->front->rear = node->rear; + else + root->front = node->rear; + if (node->rear) + node->rear->front = node->front; + else + root->rear = node->front; +} + +void lm_dll_pop_from_front(lm_dll_root *root) { + assert(root); + assert(root->front); + lm_dll_remove(root, root->front); +} diff --git a/libmisc/log.c b/libmisc/log.c new file mode 100644 index 0000000..be87de6 --- /dev/null +++ b/libmisc/log.c @@ -0,0 +1,273 @@ +/* libmisc/log.c - stdio logging + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdint.h> /* for uint{n}_t */ + +#include <libmisc/assert.h> /* for static_assert() */ + +static const char *byte_strs[] = { + "0x00", + "0x01", + "0x02", + "0x03", + "0x04", + "0x05", + "0x06", + "0x07", + "0x08", + "0x09", + "0x0A", + "0x0B", + "0x0C", + "0x0D", + "0x0E", + "0x0F", + "0x10", + "0x11", + "0x12", + "0x13", + "0x14", + "0x15", + "0x16", + "0x17", + "0x18", + "0x19", + "0x1A", + "0x1B", + "0x1C", + "0x1D", + "0x1E", + "0x1F", + "0x20", + "0x21", + "0x22", + "0x23", + "0x24", + "0x25", + "0x26", + "0x27", + "0x28", + "0x29", + "0x2A", + "0x2B", + "0x2C", + "0x2D", + "0x2E", + "0x2F", + "0x30", + "0x31", + "0x32", + "0x33", + "0x34", + "0x35", + "0x36", + "0x37", + "0x38", + "0x39", + "0x3A", + "0x3B", + "0x3C", + "0x3D", + "0x3E", + "0x3F", + "0x40", + "0x41", + "0x42", + "0x43", + "0x44", + "0x45", + "0x46", + "0x47", + "0x48", + "0x49", + "0x4A", + "0x4B", + "0x4C", + "0x4D", + "0x4E", + "0x4F", + "0x50", + "0x51", + "0x52", + "0x53", + "0x54", + "0x55", + "0x56", + "0x57", + "0x58", + "0x59", + "0x5A", + "0x5B", + "0x5C", + "0x5D", + "0x5E", + "0x5F", + "0x60", + "0x61", + "0x62", + "0x63", + "0x64", + "0x65", + "0x66", + "0x67", + "0x68", + "0x69", + "0x6A", + "0x6B", + "0x6C", + "0x6D", + "0x6E", + "0x6F", + "0x70", + "0x71", + "0x72", + "0x73", + "0x74", + "0x75", + "0x76", + "0x77", + "0x78", + "0x79", + "0x7A", + "0x7B", + "0x7C", + "0x7D", + "0x7E", + "0x7F", + "0x80", + "0x81", + "0x82", + "0x83", + "0x84", + "0x85", + "0x86", + "0x87", + "0x88", + "0x89", + "0x8A", + "0x8B", + "0x8C", + "0x8D", + "0x8E", + "0x8F", + "0x90", + "0x91", + "0x92", + "0x93", + "0x94", + "0x95", + "0x96", + "0x97", + "0x98", + "0x99", + "0x9A", + "0x9B", + "0x9C", + "0x9D", + "0x9E", + "0x9F", + "0xA0", + "0xA1", + "0xA2", + "0xA3", + "0xA4", + "0xA5", + "0xA6", + "0xA7", + "0xA8", + "0xA9", + "0xAA", + "0xAB", + "0xAC", + "0xAD", + "0xAE", + "0xAF", + "0xB0", + "0xB1", + "0xB2", + "0xB3", + "0xB4", + "0xB5", + "0xB6", + "0xB7", + "0xB8", + "0xB9", + "0xBA", + "0xBB", + "0xBC", + "0xBD", + "0xBE", + "0xBF", + "0xC0", + "0xC1", + "0xC2", + "0xC3", + "0xC4", + "0xC5", + "0xC6", + "0xC7", + "0xC8", + "0xC9", + "0xCA", + "0xCB", + "0xCC", + "0xCD", + "0xCE", + "0xCF", + "0xD0", + "0xD1", + "0xD2", + "0xD3", + "0xD4", + "0xD5", + "0xD6", + "0xD7", + "0xD8", + "0xD9", + "0xDA", + "0xDB", + "0xDC", + "0xDD", + "0xDE", + "0xDF", + "0xE0", + "0xE1", + "0xE2", + "0xE3", + "0xE4", + "0xE5", + "0xE6", + "0xE7", + "0xE8", + "0xE9", + "0xEA", + "0xEB", + "0xEC", + "0xED", + "0xEE", + "0xEF", + "0xF0", + "0xF1", + "0xF2", + "0xF3", + "0xF4", + "0xF5", + "0xF6", + "0xF7", + "0xF8", + "0xF9", + "0xFA", + "0xFB", + "0xFC", + "0xFD", + "0xFE", + "0xFF", +}; +static_assert(sizeof(byte_strs)/sizeof(byte_strs[0]) == 0x100); + +const char *const_byte_str(uint8_t b) { + return byte_strs[b]; +} 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..15f9446 --- /dev/null +++ b/libmisc/tests/test_assert.c @@ -0,0 +1,94 @@ +/* libmisc/tests/test_assert.c - Tests for <libmisc/assert.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <setjmp.h> +#include <stdarg.h> /* for va_list, va_start(), va_end() */ +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <libmisc/macro.h> +#include <libmisc/assert.h> +#include <libmisc/_intercept.h> + +#include "test.h" + +/* Intercept failures and logging *********************************************/ + +bool global_failed; +char *global_log; +jmp_buf global_env; + +#define with_intercept() ({ \ + global_failed = false; \ + if (global_log) \ + free(global_log); \ + global_log = NULL; \ + setjmp(global_env) == 0; \ + }) + +void __lm_abort(void) { + global_failed = true; + longjmp(global_env, 1); +} + +int __lm_light_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vasprintf(&global_log, format, va); + va_end(va); + return ret; +} + +#define __builtin_unreachable() test_assert(0) + +/* Utilities ******************************************************************/ + +#define test_should_succeed(test) do { \ + if (with_intercept()) { \ + test; \ + } \ + test_assert(global_failed == false); \ + test_assert(global_log == NULL); \ + } while (0) + +#define test_should_fail(test, exp_log) do { \ + if (with_intercept()) { \ + test; \ + } \ + test_assert(global_failed == true); \ + if (!(global_log != NULL && \ + strcmp(global_log, exp_log) == 0)) { \ + printf("exp = \"%s\"\n" \ + "act = \"%s\"\n", \ + exp_log, global_log); \ + test_assert(0); \ + } \ + } while (0) + +/* Actual tests ***************************************************************/ + +static_assert(sizeof(char) == 1); + +int main() { +#ifndef NDEBUG + test_should_succeed(assert(true)); + test_should_fail(assert(false), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n"); + + test_should_succeed(assert_msg(true, "foo")); + test_should_fail(assert_msg(false, "foo"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed: foo\n"); + test_should_succeed(assert_msg(true, NULL)); + test_should_fail(assert_msg(false, NULL), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"false\" failed\n"); + + test_should_fail(assert_notreached("xxx"), "error: ASSERT: "__FILE__":"LM_STR_(__LINE__)":main(): assertion \"notreached\" failed: xxx\n"); + + if (global_log) { + free(global_log); + global_log = NULL; + } +#endif + return 0; +} diff --git a/libmisc/tests/test_assert_min.c b/libmisc/tests/test_assert_min.c new file mode 100644 index 0000000..9c0394b --- /dev/null +++ b/libmisc/tests/test_assert_min.c @@ -0,0 +1,17 @@ +/* libmisc/tests/test_assert_min.c - Tests for minimal <libmisc/assert.h> + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/* Don't include *anything* else. */ +#include <libmisc/assert.h> + +static_assert(1 == 1); + +int main() { + assert_msg(1, "foo"); + assert(1); + return 0; + assert_notreached("ret"); +} diff --git a/libmisc/tests/test_endian.c b/libmisc/tests/test_endian.c new file mode 100644 index 0000000..dcb3cc2 --- /dev/null +++ b/libmisc/tests/test_endian.c @@ -0,0 +1,41 @@ +/* libmisc/tests/test_endian.c - Tests for <libmisc/endian.h> + * + * Copyright (C) 2024-2025 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[(2+4+8)*2] = {0}; + size_t pos = 0; + pos += uint16be_encode(&act[pos], UINT16_C(0x1234)); + pos += uint32be_encode(&act[pos], UINT32_C(0x56789ABC)); + pos += uint64be_encode(&act[pos], UINT64_C(0xAC589A93278CB30A)); + pos += uint16le_encode(&act[pos], UINT16_C(0x1234)); + pos += uint32le_encode(&act[pos], UINT32_C(0x56789ABC)); + pos += uint64le_encode(&act[pos], UINT64_C(0xAC589A93278CB30A)); + + test_assert(pos == sizeof(act)); + uint8_t exp[(2+4+8)*2] = { 0x12, 0x34, + 0x56, 0x78, 0x9A, 0xBC, + 0xAC, 0x58, 0x9A, 0x93, 0x27, 0x8C, 0xB3, 0x0A, + 0x34, 0x12, + 0xBC, 0x9A, 0x78, 0x56, + 0x0A, 0xB3, 0x8C, 0x27, 0x93, 0x9A, 0x58, 0xAC}; + test_assert(memcmp(act, exp, sizeof(act)) == 0); + + pos = 0; + test_assert(uint16be_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2; + test_assert(uint32be_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4; + test_assert(uint64be_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8; + test_assert(uint16le_decode(&act[pos]) == UINT16_C(0x1234)); pos += 2; + test_assert(uint32le_decode(&act[pos]) == UINT32_C(0x56789ABC)); pos += 4; + test_assert(uint64le_decode(&act[pos]) == UINT64_C(0xAC589A93278CB30A)); pos += 8; + + 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_log.c b/libmisc/tests/test_log.c new file mode 100644 index 0000000..49a76ca --- /dev/null +++ b/libmisc/tests/test_log.c @@ -0,0 +1,70 @@ +/* libmisc/tests/test_log.c - Tests for <libmisc/log.h> + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#define _GNU_SOURCE /* for vasprintf() */ +#include <stdarg.h> /* for va_list */ +#include <stdio.h> /* for vasprintf() */ +#include <stdlib.h> /* for free() */ +#include <string.h> /* for strcmp() */ + +#define LOG_NAME FROBNICATE +#include <libmisc/log.h> + +#include <libmisc/_intercept.h> + +#include "test.h" + +/* Intercept output ***********************************************************/ + +static char *log_output = NULL; + +int __lm_printf(const char *format, ...) { + va_list va; + va_start(va, format); + int ret = vasprintf(&log_output, format, va); + va_end(va); + return ret; +} + +/* Actual tests ***************************************************************/ + +#define should_print(_exp, cmd) do { \ + char *exp = _exp; \ + test_assert(!log_output); \ + cmd; \ + if (!exp) \ + test_assert(!log_output); \ + else { \ + test_assert(log_output); \ + if (strcmp(log_output, exp)) { \ + printf("exp = \"%s\"\n" \ + "act = \"%s\"\n", \ + exp, log_output); \ + test_assert(0); \ + } \ + } \ + if (log_output) { \ + free(log_output); \ + log_output = NULL; \ + } \ + } while (0) + +int main() { + should_print("error: FROBNICATE: val=42\n", + errorf("val=%d", 42)); + should_print("info : FROBNICATE: val=0\n", + infof("val=%d", 0)); +#ifndef NDEBUG +#define CONFIG_FROBNICATE_DEBUG 1 + should_print("debug: FROBNICATE: val=-2\n", + debugf("val=%d", -2)); +#undef CONFIG_FROBNICATE_DEBUG +#define CONFIG_FROBNICATE_DEBUG 0 + should_print(NULL, + debugf("val=%d", -2)); +#endif + return 0; +} diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c new file mode 100644 index 0000000..1320eb3 --- /dev/null +++ b/libmisc/tests/test_macro.c @@ -0,0 +1,54 @@ +/* libmisc/tests/test_macro.c - Tests for <libmisc/macro.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/macro.h> + +#include "test.h" + +int main() { + printf("== LM_NEXT_POWER_OF_2 =====================================\n"); + /* valid down to 0. */ + test_assert(LM_NEXT_POWER_OF_2(0) == 1); + test_assert(LM_NEXT_POWER_OF_2(1) == 2); + test_assert(LM_NEXT_POWER_OF_2(2) == 4); + test_assert(LM_NEXT_POWER_OF_2(3) == 4); + test_assert(LM_NEXT_POWER_OF_2(4) == 8); + test_assert(LM_NEXT_POWER_OF_2(5) == 8); + test_assert(LM_NEXT_POWER_OF_2(6) == 8); + test_assert(LM_NEXT_POWER_OF_2(7) == 8); + test_assert(LM_NEXT_POWER_OF_2(8) == 16); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(16) == 32); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(0x7000000000000000) == 0x8000000000000000); + /* ... */ + test_assert(LM_NEXT_POWER_OF_2(0x8000000000000000-1) == 0x8000000000000000); + /* Valid up to 0x8000000000000000-1 = (1<<63)-1 */ + + printf("== LM_FLOORLOG2 ===========================================\n"); + /* valid down to 1. */ + test_assert(LM_FLOORLOG2(1) == 0); + test_assert(LM_FLOORLOG2(2) == 1); + test_assert(LM_FLOORLOG2(3) == 1); + test_assert(LM_FLOORLOG2(4) == 2); + test_assert(LM_FLOORLOG2(5) == 2); + test_assert(LM_FLOORLOG2(6) == 2); + test_assert(LM_FLOORLOG2(7) == 2); + test_assert(LM_FLOORLOG2(8) == 3); + /* ... */ + test_assert(LM_FLOORLOG2(16) == 4); + /* ... */ + test_assert(LM_FLOORLOG2(0x80000000) == 31); + /* ... */ + test_assert(LM_FLOORLOG2(0xFFFFFFFF) == 31); + test_assert(LM_FLOORLOG2(0x100000000) == 32); + /* ... */ + test_assert(LM_FLOORLOG2(0x8000000000000000) == 63); + /* ... */ + test_assert(LM_FLOORLOG2(0xFFFFFFFFFFFFFFFF) == 63); + + return 0; +} diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c new file mode 100644 index 0000000..024dddb --- /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-2025 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..8076155 --- /dev/null +++ b/libmisc/tests/test_rand.c @@ -0,0 +1,79 @@ +/* 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 <libmisc/_intercept.h> + +#include "test.h" + +/* Intercept failures *********************************************************/ + +#ifndef NDEBUG +jmp_buf *__catch; + +void __lm_abort(void) { + if (__catch) + longjmp(*__catch, 1); + 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); +#endif + +/* 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) { +#ifndef NDEBUG + should_abort(rand_uint63n(cnt)); +#else + return; +#endif + } else { + double sum = 0; + bool seen[MAX_SEE_ALL] = {0}; + for (int i = 0; i < ROUNDS; i++) { + uint64_t val = rand_uint63n(cnt); + sum += val; + test_assert(val < cnt); + if (cnt < MAX_SEE_ALL) + seen[val] = true; + } + if (cnt > 1) { + test_assert(sum/ROUNDS > 0.45*(cnt-1)); + test_assert(sum/ROUNDS < 0.55*(cnt-1)); + } + 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; +} |