From 791e01cf47514dc05ffd9a6c69f7320e3af6c62a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Mon, 2 Dec 2024 16:52:04 -0500
Subject: [PATCH] Just fiddling with C23...

At the same time, playing with testing in C... started at the
coverage test level and working up to see how painful it really is.

This is just a linked-list queue thing the sort that every C hacker
has implemented countless times... but if it ends up as "good", it
could be used in SyncTERM in the future.
---
 src/syncterm/queue/CMakeLists.txt        |  25 ++
 src/syncterm/queue/llq.h                 |  71 ++++++
 src/syncterm/queue/llq_alloc.c           |  50 ++++
 src/syncterm/queue/llq_dq.c              |  86 +++++++
 src/syncterm/queue/llq_free.c            |  32 +++
 src/syncterm/queue/llq_get_err.c         |  12 +
 src/syncterm/queue/llq_int.h             |  78 +++++++
 src/syncterm/queue/llq_nq.c              |  30 +++
 src/syncterm/queue/llq_was_mt.c          |  16 ++
 src/syncterm/queue/test/CMakeLists.txt   |  12 +
 src/syncterm/queue/test/test_llq.c       |  20 ++
 src/syncterm/queue/test/test_llq_alloc.c | 279 +++++++++++++++++++++++
 12 files changed, 711 insertions(+)
 create mode 100644 src/syncterm/queue/CMakeLists.txt
 create mode 100644 src/syncterm/queue/llq.h
 create mode 100644 src/syncterm/queue/llq_alloc.c
 create mode 100644 src/syncterm/queue/llq_dq.c
 create mode 100644 src/syncterm/queue/llq_free.c
 create mode 100644 src/syncterm/queue/llq_get_err.c
 create mode 100644 src/syncterm/queue/llq_int.h
 create mode 100644 src/syncterm/queue/llq_nq.c
 create mode 100644 src/syncterm/queue/llq_was_mt.c
 create mode 100644 src/syncterm/queue/test/CMakeLists.txt
 create mode 100644 src/syncterm/queue/test/test_llq.c
 create mode 100644 src/syncterm/queue/test/test_llq_alloc.c

diff --git a/src/syncterm/queue/CMakeLists.txt b/src/syncterm/queue/CMakeLists.txt
new file mode 100644
index 0000000000..1aabddaaa7
--- /dev/null
+++ b/src/syncterm/queue/CMakeLists.txt
@@ -0,0 +1,25 @@
+# 3.21 added C23
+# 3.23 added file sets
+cmake_minimum_required(VERSION 3.31)
+project(llq VERSION 0.0 LANGUAGES C)
+
+add_library(llq)
+set_property(TARGET llq PROPERTY C_STANDARD 23)
+set_property(TARGET llq PROPERTY C_STANDARD_REQUIRED ON)
+target_sources(llq
+	PRIVATE llq_alloc.c llq_dq.c llq_free.c llq_get_err.c llq_nq.c llq_was_mt.c
+	INTERFACE FILE_SET HEADERS FILES llq.h
+)
+
+target_include_directories(llq INTERFACE .)
+target_link_libraries(llq INTERFACE stdthreads)
+target_compile_definitions(llq PRIVATE $<$<CONFIG:Release>:NDEBUG>)
+
+option(BUILD_TESTING "Perform unit tests after build" OFF)
+if (BUILD_TESTING)
+	add_subdirectory(test)
+	enable_testing()
+	add_test(NAME llq_alloc COMMAND test_llq_alloc)
+endif (BUILD_TESTING)
+
+install(TARGETS llq FILE_SET HEADERS)
diff --git a/src/syncterm/queue/llq.h b/src/syncterm/queue/llq.h
new file mode 100644
index 0000000000..1b046a05d9
--- /dev/null
+++ b/src/syncterm/queue/llq.h
@@ -0,0 +1,71 @@
+#ifndef DEUCE_QUEUE_H
+#define DEUCE_QUEUE_H
+
+#include <stdalign.h>
+#include <stdint.h>
+#include <threads.h>
+
+typedef struct linked_list_queue *llq;
+typedef intptr_t llq_err_t;
+
+enum llq_errval : llq_err_t {
+	llq_err_none,
+	llq_err_badflags,
+	llq_err_calloc,
+	llq_err_cnd_broadcast,
+	llq_err_cnd_init,
+	llq_err_cnd_signal,
+	llq_err_cnd_timedwait,
+	llq_err_cnd_wait,
+	llq_err_null_llq,
+	llq_err_mtx_destroy,
+	llq_err_mtx_init,
+	llq_err_mtx_lock,
+	llq_err_mtx_unlock,
+	llq_err_not_empty,
+	llq_err_timedout,
+	llq_err_tss_create,
+};
+
+/*
+ * Allocates a queue.  flags must be zero.
+ * If flags is zero, the queue is FIFO
+ * If err is not nullptr, will be filled if llq == nullptr
+ */
+llq llq_alloc(llq_err_t *err, uint32_t flags, ...);
+
+/*
+ * Frees a queue allocated with llq_alloc()
+ * if err isn't nullptr, will be filled if an error occurs
+ * in the case of errors, llq_free() pretends there were no errors.
+ * Hopefully this will crash your program.  You're welcome.
+ */
+void llq_free(llq restrict llq, llq_err_t *err);
+
+// Enqueues data in llq
+bool llq_nq(llq restrict llq, void * restrict data);
+
+// Dequeues an enqueued value from llq
+void * llq_dq(llq restrict llq);
+
+/*
+ * Blocks for ts time or until it can dequeue a value from llq.
+ * if ts is nullptr, blocks forever.
+ */
+void * llq_dq_wait(llq restrict llq, const struct timespec * restrict ts);
+
+/*
+ * Checks llq for an item.  If there was an item in llq when
+ * checked, returns true.  Returns false in all other cases
+ */
+bool llq_was_empty(llq restrict llq);
+
+/*
+ * Gets the error status of the queue.
+ * Must be called from the same thread where the error occured.
+ * msg is only valid until the next call to any llq_*() function.
+ * Returns true if no errors occured while getting the error. hehhehheh.
+ */
+bool llq_get_err(llq restrict llq, llq_err_t * restrict num, char8_t ** restrict msg);
+
+#endif
diff --git a/src/syncterm/queue/llq_alloc.c b/src/syncterm/queue/llq_alloc.c
new file mode 100644
index 0000000000..21d36c67a9
--- /dev/null
+++ b/src/syncterm/queue/llq_alloc.c
@@ -0,0 +1,50 @@
+#include <stdlib.h>
+
+#include "llq_int.h"
+
+llq
+llq_alloc(llq_err_t *err, uint32_t flags, ...)
+{
+	if (flags != 0) {
+		SET_ERR(err, llq_err_badflags);
+		return nullptr;
+	}
+
+	llq ret = calloc(1, sizeof(struct linked_list_queue));
+	if (ret == nullptr) {
+		SET_ERR(err, llq_err_calloc);
+		return nullptr;
+	}
+	ret->head = nullptr;
+	ret->tail = nullptr;
+	if (mtx_init(&ret->mtx, mtx_plain) != thrd_success) {
+		SET_ERR(err, llq_err_mtx_init);
+		free(ret);
+		return nullptr;
+	}
+	if (cnd_init(&ret->cnd) != thrd_success) {
+		SET_ERR(err, llq_err_cnd_init);
+		mtx_destroy(&ret->mtx);
+		free(ret);
+		return nullptr;
+	}
+	if (tss_create(&ret->errnum, nullptr) != thrd_success) {
+		SET_ERR(err, llq_err_tss_create);
+		cnd_destroy(&ret->cnd);
+		mtx_destroy(&ret->mtx);
+		free(ret);
+		return nullptr;
+	}
+	if (tss_create(&ret->errmsg, free) != thrd_success) {
+		SET_ERR(err, llq_err_tss_create);
+		tss_delete(ret->errnum);
+		cnd_destroy(&ret->cnd);
+		mtx_destroy(&ret->mtx);
+		free(ret);
+		return nullptr;
+	}
+	if (err)
+		*err = llq_err_none;
+
+	return ret;
+}
diff --git a/src/syncterm/queue/llq_dq.c b/src/syncterm/queue/llq_dq.c
new file mode 100644
index 0000000000..faf3d5ca61
--- /dev/null
+++ b/src/syncterm/queue/llq_dq.c
@@ -0,0 +1,86 @@
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "llq_int.h"
+
+static void *
+dq_locked(llq restrict llq)
+{
+	assert(llq != nullptr && "llq is nullptr");
+	if (llq->head == nullptr)
+		return nullptr;
+	struct linked_list_queue_entry * restrict qe = llq->head;
+	llq->head = qe->next;
+	if (llq->tail == qe)
+		llq->tail = nullptr;
+	return qe;
+}
+
+void *
+llq_dq(llq restrict llq)
+{
+	void * restrict ret;
+	struct linked_list_queue_entry * restrict qe;
+
+	CHECK_Q(llq, nullptr);
+	MTX_LOCK(llq, nullptr);
+	qe = dq_locked(llq);
+	MTX_UNLOCK(llq);
+
+	if (qe == nullptr)
+		return nullptr;
+	ret = qe->data;
+	free(qe);
+	return ret;
+}
+
+#define TRY_DQ(llq) do {                                              \
+	struct linked_list_queue_entry * restrict qe = dq_locked(llq); \
+	if (qe != nullptr) {                                            \
+		MTX_UNLOCK(llq);                                         \
+		void * restrict ret = qe->data;                           \
+		free(qe);                                                  \
+		return ret;                                                 \
+	}                                                                    \
+} while(0)
+
+static void *
+dq_wait_forever(llq restrict llq)
+{
+	for (;;) {
+		if (cnd_wait(&llq->cnd, &llq->mtx) != thrd_success) {
+			SET_ERROR(llq, llq_err_cnd_wait, "cnd_wait() failed");
+			MTX_UNLOCK(llq);
+			return nullptr;
+		}
+		TRY_DQ(llq);
+	}
+	unreachable();
+}
+
+static void *
+dq_wait_timed(llq restrict llq, const struct timespec * restrict ts)
+{
+	if (cnd_timedwait(&llq->cnd, &llq->mtx, ts) != thrd_success) {
+		SET_ERROR(llq, llq_err_cnd_timedwait, "cnd_timedwait() failed");
+		MTX_UNLOCK(llq);
+		return nullptr;
+	}
+
+	TRY_DQ(llq);
+	SET_ERROR(llq, llq_err_timedout, "timeout expired");
+	MTX_UNLOCK(llq);
+	return nullptr;
+}
+
+void *
+llq_dq_wait(llq restrict llq, const struct timespec * restrict ts)
+{
+	CHECK_Q(llq, nullptr);
+	MTX_LOCK(llq, nullptr);
+	TRY_DQ(llq);
+
+	if (ts == nullptr)
+		return dq_wait_forever(llq);
+	return dq_wait_timed(llq, ts);
+}
diff --git a/src/syncterm/queue/llq_free.c b/src/syncterm/queue/llq_free.c
new file mode 100644
index 0000000000..29329b28d2
--- /dev/null
+++ b/src/syncterm/queue/llq_free.c
@@ -0,0 +1,32 @@
+#include <stdlib.h>
+
+#include "llq_int.h"
+
+void
+llq_free(llq restrict llq, llq_err_t *err)
+{
+	if (llq == nullptr) {
+		SET_ERR(err, llq_err_null_llq);
+		return;
+	}
+	if (mtx_lock(&llq->mtx) != thrd_success)
+		SET_ERR(err, llq_err_mtx_lock);
+	for (struct linked_list_queue_entry * restrict qe = llq->head; qe; qe = llq->head) {
+		SET_ERR(err, llq_err_not_empty);
+		llq->head = qe->next;
+		free(qe);
+	}
+	// Try to make anyone else waiting crash...
+	// We could memset these things to make it more likely. 😈
+	tss_delete(llq->errnum);
+	tss_delete(llq->errmsg);
+	if (cnd_broadcast(&llq->cnd) != thrd_success)
+		SET_ERR(err, llq_err_cnd_broadcast);
+	cnd_destroy(&llq->cnd);
+	if (mtx_unlock(&llq->mtx) != thrd_success)
+		SET_ERR(err, llq_err_mtx_unlock);
+	mtx_destroy(&llq->mtx);
+	if (err)
+		*err = llq_err_none;
+	free(llq);
+}
diff --git a/src/syncterm/queue/llq_get_err.c b/src/syncterm/queue/llq_get_err.c
new file mode 100644
index 0000000000..9ec5ab7993
--- /dev/null
+++ b/src/syncterm/queue/llq_get_err.c
@@ -0,0 +1,12 @@
+#include "llq_int.h"
+
+bool
+llq_get_err(llq restrict llq, llq_err_t * restrict num, char8_t ** restrict msg)
+{
+	CHECK_Q(llq, false);
+	if (num != nullptr)
+		*num = (llq_err_t)tss_get(llq->errnum);
+	if (msg != nullptr)
+		*msg = (char8_t *)tss_get(llq->errnum);
+	return true;
+}
diff --git a/src/syncterm/queue/llq_int.h b/src/syncterm/queue/llq_int.h
new file mode 100644
index 0000000000..e0305ecbbb
--- /dev/null
+++ b/src/syncterm/queue/llq_int.h
@@ -0,0 +1,78 @@
+#ifndef DEUCE_QUEUE_INT_H
+#define DEUCE_QUEUE_INT_H
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Needed to test asserts
+#ifdef UNIT_TESTING
+extern void mock_assert(const int result, const char* const expression,
+                        const char * const file, const int line);
+
+#undef assert
+#define assert(...) my_mock_assert((int)(__VA_ARGS__), #__VA_ARGS__, __FILE__, __LINE__);
+#endif
+
+#include "llq.h"
+
+#ifndef CACHE_LINE_SIZE
+#define CACHE_LINE_SIZE 64
+#endif
+
+#define SET_ERROR(llq, num, msg) do { \
+	assert(!msg);                  \
+	free(tss_get(llq->errmsg));     \
+	char *amsg = strdup(msg);        \
+	tss_set(llq->errmsg, amsg);       \
+} while(0)
+
+#define SET_ERR(err, val) do { \
+	assert(!val);           \
+	if (err != nullptr)      \
+		*err = val;       \
+} while(0)
+
+#define MTX_LOCK(llq, failval) do {                               \
+	int err = mtx_lock(&llq->mtx);                             \
+	if (err != thrd_success) {                                  \
+		assert(!"msg_lock failed!");                         \
+		SET_ERROR(llq, llq_err_mtx_lock, u8"failed to lock"); \
+		/* TODO: Figure out debug log stuff... */              \
+		return failval;                                         \
+	}                                                                \
+} while (0)
+
+#define MTX_UNLOCK(llq) do {                                          \
+	int err = mtx_unlock(&llq->mtx);                               \
+	if (err != thrd_success) {                                      \
+		assert(!"mtx_unlock failed!");                           \
+		SET_ERROR(llq, llq_err_mtx_unlock, u8"failed to unlock"); \
+		/* TODO: Figure out debug log stuff... */                  \
+	}                                                                   \
+} while (0)
+
+#define CHECK_Q(llq, failval) do {                            \
+	assert(llq != nullptr && "llq is null");               \
+	if (llq == nullptr) {                                   \
+		SET_ERROR(llq, llq_err_null_llq, "llq is null"); \
+		return failval;                                   \
+	}                                                          \
+} while(0)
+
+struct linked_list_queue_entry {
+	struct linked_list_queue_entry *next;
+	void *data;
+};
+
+struct linked_list_queue {
+	mtx_t mtx;
+	struct linked_list_queue_entry *head;
+	struct linked_list_queue_entry *tail;
+	tss_t errnum;
+	tss_t errmsg;
+	// TODO: Add a cache for allocated entries...
+	alignas((sizeof(mtx_t) + sizeof(struct linked_list_queue_entry *) * 2) > CACHE_LINE_SIZE ? 0 : CACHE_LINE_SIZE) cnd_t cnd;
+};
+
+#endif
diff --git a/src/syncterm/queue/llq_nq.c b/src/syncterm/queue/llq_nq.c
new file mode 100644
index 0000000000..99a786d8ca
--- /dev/null
+++ b/src/syncterm/queue/llq_nq.c
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+
+#include "llq_int.h"
+
+bool
+llq_nq(llq restrict llq, void * restrict data)
+{
+	CHECK_Q(llq, false);
+
+	struct linked_list_queue_entry * restrict qe = calloc(1, sizeof(struct linked_list_queue_entry));
+	if (qe == nullptr) {
+		MTX_LOCK(llq, false);
+		SET_ERROR(llq, llq_err_calloc, "unable to calloc new entry");
+		MTX_UNLOCK(llq);
+		return false;
+	}
+	qe->next = nullptr;
+	qe->data = data;
+	MTX_LOCK(llq, false);
+	if (llq->tail != nullptr)
+		llq->tail->next = qe;
+	if (llq->head == nullptr) {
+		llq->head = qe;
+		if (cnd_signal(&llq->cnd) != thrd_success)
+			SET_ERROR(llq, llq_err_cnd_signal, "cnd_signal() failed");
+	}
+	llq->tail = qe;
+	MTX_UNLOCK(llq);
+	return true;
+}
diff --git a/src/syncterm/queue/llq_was_mt.c b/src/syncterm/queue/llq_was_mt.c
new file mode 100644
index 0000000000..d98b7b0dca
--- /dev/null
+++ b/src/syncterm/queue/llq_was_mt.c
@@ -0,0 +1,16 @@
+#include <stdlib.h>
+
+#include "llq_int.h"
+
+bool
+llq_was_mt(llq restrict llq)
+{
+	bool ret;
+
+	CHECK_Q(llq, false);
+	MTX_LOCK(llq, nullptr);
+	ret = (llq->head == nullptr);
+	MTX_UNLOCK(llq);
+
+	return ret;
+}
diff --git a/src/syncterm/queue/test/CMakeLists.txt b/src/syncterm/queue/test/CMakeLists.txt
new file mode 100644
index 0000000000..523a0829d8
--- /dev/null
+++ b/src/syncterm/queue/test/CMakeLists.txt
@@ -0,0 +1,12 @@
+# 3.21 added C23
+cmake_minimum_required(VERSION 3.31)
+find_package(PkgConfig REQUIRED)
+
+pkg_check_modules(CMocka cmocka REQUIRED)
+
+add_executable(test_llq_alloc test_llq_alloc.c)
+set_property(TARGET test_llq_alloc PROPERTY C_STANDARD 23)
+target_compile_definitions(test_llq_alloc PRIVATE UNIT_TESTING)
+target_include_directories(test_llq_alloc PRIVATE ${CMocka_INCLUDE_DIRS} ${llq_SOURCE_DIR})
+target_link_directories(test_llq_alloc PRIVATE ${CMocka_LIBRARY_DIRS})
+target_link_libraries(test_llq_alloc ${CMocka_LIBRARIES})
diff --git a/src/syncterm/queue/test/test_llq.c b/src/syncterm/queue/test/test_llq.c
new file mode 100644
index 0000000000..5905386333
--- /dev/null
+++ b/src/syncterm/queue/test/test_llq.c
@@ -0,0 +1,20 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+
+#include <cmocka.h>
+
+#include "llq.h"
+
+static llq llq;
+
+
+
+main(int argc, char **argv)
+{
+	const struct CMUnitTest tests[] = {
+	};
+
+	return cmocka_run_group_tests(tests, nullptr, nullptr);
+}
diff --git a/src/syncterm/queue/test/test_llq_alloc.c b/src/syncterm/queue/test/test_llq_alloc.c
new file mode 100644
index 0000000000..0dcfb352d4
--- /dev/null
+++ b/src/syncterm/queue/test/test_llq_alloc.c
@@ -0,0 +1,279 @@
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <stdlib.h>
+
+extern void _test_free(void* const ptr, const char* file, const int line);
+void
+test_free(void *ptr) {
+	_test_free(ptr, "Unknown", -1);
+}
+
+#include <cmocka.h>
+
+#include "llq.h"
+
+int
+mtx_init(mtx_t *mtx, int type)
+{
+	return mock();
+}
+
+void
+mtx_destroy(mtx_t *mtx)
+{
+	function_called();
+}
+
+int
+cnd_init(cnd_t *cnd)
+{
+	return mock();
+}
+
+void
+cnd_destroy(cnd_t *cnd)
+{
+	function_called();
+}
+
+int
+tss_create(tss_t *key, void (*dtor)(void *))
+{
+	return mock();
+}
+
+void
+tss_delete(tss_t key)
+{
+	function_called();
+}
+
+static int assert_count = -1;
+static void
+my_mock_assert(const int result, const char* const expression,
+                 const char * const file, const int line)
+{
+	if (assert_count == -1)
+		fail();
+	assert_count++;
+}
+
+static int malloc_count = -1;
+static void *
+my_test_calloc(size_t number, const size_t size, const char *file, const int line)
+{
+	if (malloc_count == 0) {
+		errno = ENOMEM;
+		return nullptr;
+	}
+	if (malloc_count > 0)
+		malloc_count--;
+	return _test_calloc(number, size, file, line);
+}
+
+void
+test_setup()
+{
+	assert_count = -1;
+	malloc_count = -1;
+}
+
+#define llq_test_err(atype, flags, err) \
+	llq_err_t e;                     \
+	void *ret;                        \
+	e = (llq_err_t)&e;                 \
+	assert_count = 0;                   \
+	atype(ret = llq_alloc(&e, flags));   \
+	free(ret);                            \
+	assert_true(e == err)
+
+#define llq_test_noerr(atype, flags)  \
+	void *ret;                     \
+	assert_count = 0;               \
+	ret = llq_alloc(nullptr, flags); \
+	atype(ret);                       \
+	free(ret);
+
+void
+illegal_flags_error(void **state)
+{
+	test_setup();
+	llq_test_err(assert_null, 1, llq_err_badflags);
+	assert_true(assert_count == 1);
+}
+
+void
+illegal_flags_noerror(void **state)
+{
+	test_setup();
+	llq_test_noerr(assert_null, 1);
+	assert_true(assert_count == 1);
+}
+
+void
+calloc_failed_error(void **state)
+{
+	test_setup();
+	malloc_count = 0;
+	llq_test_err(assert_null, 0, llq_err_calloc);
+	assert_true(assert_count == 1);
+}
+
+void
+calloc_failed_noerror(void **state)
+{
+	test_setup();
+	malloc_count = 0;
+	llq_test_noerr(assert_null, 0);
+	assert_true(assert_count == 1);
+}
+
+void
+mtx_init_failed_error(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_error);
+	llq_test_err(assert_null, 0, llq_err_mtx_init);
+	assert_true(assert_count == 1);
+}
+
+void
+mtx_init_failed_noerror(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_error);
+	llq_test_noerr(assert_null, 0);
+	assert_true(assert_count == 1);
+}
+
+void
+cnd_init_failed_error(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_error);
+	expect_function_call(mtx_destroy);
+	llq_test_err(assert_null, 0, llq_err_cnd_init);
+	assert_true(assert_count == 1);
+}
+
+void
+cnd_init_failed_noerror(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_error);
+	expect_function_call(mtx_destroy);
+	llq_test_noerr(assert_null, 0);
+	assert_true(assert_count == 1);
+}
+
+void
+tss_create_failed_error(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_error);
+	expect_function_call(cnd_destroy);
+	expect_function_call(mtx_destroy);
+	llq_test_err(assert_null, 0, llq_err_tss_create);
+	assert_true(assert_count == 1);
+}
+
+void
+tss_create_failed_noerror(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_error);
+	expect_function_call(cnd_destroy);
+	expect_function_call(mtx_destroy);
+	llq_test_noerr(assert_null, 0);
+	assert_true(assert_count == 1);
+}
+
+void
+tss_create_failed_error2(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_success);
+	will_return(tss_create, thrd_error);
+	expect_function_call(tss_delete);
+	expect_function_call(cnd_destroy);
+	expect_function_call(mtx_destroy);
+	llq_test_err(assert_null, 0, llq_err_tss_create);
+	assert_true(assert_count == 1);
+}
+
+void
+tss_create_failed_noerror2(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_success);
+	will_return(tss_create, thrd_error);
+	expect_function_call(tss_delete);
+	expect_function_call(cnd_destroy);
+	expect_function_call(mtx_destroy);
+	llq_test_noerr(assert_null, 0);
+	assert_true(assert_count == 1);
+}
+
+void
+success_error(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_success);
+	will_return(tss_create, thrd_success);
+	llq_test_err(assert_non_null, 0, llq_err_none);
+	assert_true(assert_count == 0);
+}
+
+void
+success_noerror(void **state)
+{
+	test_setup();
+	will_return(mtx_init, thrd_success);
+	will_return(cnd_init, thrd_success);
+	will_return(tss_create, thrd_success);
+	will_return(tss_create, thrd_success);
+	llq_test_noerr(assert_non_null, 0);
+	assert_true(assert_count == 0);
+}
+
+int
+main(int argc, char **argv)
+{
+	const struct CMUnitTest tests[] = {
+		cmocka_unit_test(illegal_flags_error),
+		cmocka_unit_test(illegal_flags_noerror),
+		cmocka_unit_test(calloc_failed_error),
+		cmocka_unit_test(calloc_failed_noerror),
+		cmocka_unit_test(mtx_init_failed_error),
+		cmocka_unit_test(mtx_init_failed_noerror),
+		cmocka_unit_test(cnd_init_failed_error),
+		cmocka_unit_test(cnd_init_failed_noerror),
+		cmocka_unit_test(tss_create_failed_error),
+		cmocka_unit_test(tss_create_failed_noerror),
+		cmocka_unit_test(tss_create_failed_error2),
+		cmocka_unit_test(tss_create_failed_noerror2),
+		cmocka_unit_test(success_error),
+		cmocka_unit_test(success_noerror),
+	};
+
+	return cmocka_run_group_tests(tests, nullptr, nullptr);
+}
+
+#undef test_calloc
+#define test_calloc(number, size) my_test_calloc(number, size, __FILE__, __LINE__)
+#include "../llq_alloc.c"
-- 
GitLab