]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
threading: add unittests for cpu affinity YAML parsing
authorLukas Sismis <lsismis@oisf.net>
Sat, 24 May 2025 10:25:23 +0000 (12:25 +0200)
committerVictor Julien <victor@inliniac.net>
Sat, 7 Jun 2025 08:36:43 +0000 (10:36 +0200)
src/runmode-unittests.c
src/util-affinity.c

index f11cce5fb3643abcf6a6a827e18ddd9f441e0606..d4e57e9fe62ce0b35922bb81405d922c0364fa00 100644 (file)
@@ -79,6 +79,7 @@
 #include "util-memcmp.h"
 #include "util-misc.h"
 #include "util-signal.h"
+#include "util-affinity.h"
 
 #include "reputation.h"
 #include "util-atomic.h"
@@ -197,6 +198,7 @@ static void RegisterUnittests(void)
     SCLogRegisterTests();
     MagicRegisterTests();
     UtilMiscRegisterTests();
+    ThreadingAffinityRegisterTests();
     DetectAddressTests();
     DetectProtoTests();
     DetectPortTests();
index 501f3357329ead3509bab2947138eb12da3106c6..8707aa5fdcf76b37a012bdbb8fa68deac6626e62 100644 (file)
 #define _THREAD_AFFINITY
 #include "util-affinity.h"
 #include "conf.h"
+#include "conf-yaml-loader.h"
 #include "runmodes.h"
 #include "util-cpu.h"
 #include "util-byte.h"
 #include "util-debug.h"
 #include "util-dpdk.h"
+#include "util-unittest.h"
 
 ThreadsAffinityType thread_affinity[MAX_CPU_SET] = {
     {
@@ -1092,3 +1094,929 @@ void UtilAffinityCpusExclude(ThreadsAffinityType *mod_taf, ThreadsAffinityType *
     SCMutexUnlock(&mod_taf->taf_mutex);
 }
 #endif /* HAVE_DPDK */
+
+#ifdef UNITTESTS
+// avoiding Darwin/MacOS as it does not support bitwise CPU affinity
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
+
+/**
+ * \brief Helper function to reset affinity state for unit tests
+ * This properly clears CPU sets without destroying initialized mutexes
+ */
+static void ResetAffinityForTest(void)
+{
+    thread_affinity_init_done = 0;
+    for (int i = 0; i < MAX_CPU_SET; i++) {
+        ThreadsAffinityType *taf = &thread_affinity[i];
+        CPU_ZERO(&taf->cpu_set);
+        CPU_ZERO(&taf->lowprio_cpu);
+        CPU_ZERO(&taf->medprio_cpu);
+        CPU_ZERO(&taf->hiprio_cpu);
+        taf->nb_threads = 0;
+        taf->prio = PRIO_LOW;
+        taf->mode_flag = BALANCED_AFFINITY;
+
+        for (int j = 0; j < MAX_NUMA_NODES; j++) {
+            taf->lcpu[j] = 0;
+        }
+
+        if (taf->children) {
+            for (uint32_t j = 0; j < taf->nb_children; j++) {
+                if (taf->children[j]) {
+                    SCFree((void *)taf->children[j]->name);
+                    SCFree(taf->children[j]);
+                }
+            }
+            SCFree(taf->children);
+            taf->children = NULL;
+        }
+        taf->nb_children = 0;
+        taf->nb_children_capacity = 0;
+
+        if (i == MANAGEMENT_CPU_SET) {
+            taf->name = "management-cpu-set";
+        } else if (i == WORKER_CPU_SET) {
+            taf->name = "worker-cpu-set";
+        } else if (i == VERDICT_CPU_SET) {
+            taf->name = "verdict-cpu-set";
+        } else if (i == RECEIVE_CPU_SET) {
+            taf->name = "receive-cpu-set";
+        } else {
+            taf->name = NULL;
+        }
+
+        // Don't touch the mutex - it should remain initialized
+    }
+}
+
+/**
+ * \brief Test basic CPU affinity parsing in new format
+ */
+static int ThreadingAffinityTest01(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    management-cpu-set:\n"
+                         "      cpu: [ 0 ]\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 1, 2, 3 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+
+    AffinitySetupLoadFromConfig();
+    FAIL_IF_NOT(AffinityConfigIsLegacy() == false);
+
+    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(0, &mgmt_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&mgmt_taf->cpu_set) == 1);
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set));
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test deprecated CPU affinity format parsing
+ */
+static int ThreadingAffinityTest02(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - worker-cpu-set:\n"
+                         "        cpu: [ 1, 2 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+    FAIL_IF_NOT(AffinityConfigIsLegacy() == true);
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 2);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test CPU range parsing ("0-3")
+ */
+static int ThreadingAffinityTest03(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ \"0-3\" ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 4);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test mixed CPU specification parsing (individual CPUs in list)
+ */
+static int ThreadingAffinityTest04(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 1, 3, 5 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(5, &worker_taf->cpu_set));
+    FAIL_IF(CPU_ISSET(0, &worker_taf->cpu_set));
+    FAIL_IF(CPU_ISSET(2, &worker_taf->cpu_set));
+    FAIL_IF(CPU_ISSET(4, &worker_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 3);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test "all" CPU specification
+ */
+static int ThreadingAffinityTest05(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ \"all\" ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == UtilCpuGetNumProcessorsOnline());
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test priority settings parsing
+ */
+static int ThreadingAffinityTest06(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 0, 1, 2, 3 ]\n"
+                         "      prio:\n"
+                         "        low: [ 0 ]\n"
+                         "        medium: [ \"1-2\" ]\n"
+                         "        high: [ 3 ]\n"
+                         "        default: \"medium\"\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->lowprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->medprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->medprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->hiprio_cpu));
+    FAIL_IF_NOT(worker_taf->prio == PRIO_MEDIUM);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test mode settings (exclusive/balanced)
+ */
+static int ThreadingAffinityTest07(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 0, 1 ]\n"
+                         "      mode: \"exclusive\"\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->mode_flag == EXCLUSIVE_AFFINITY);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test threads count parsing
+ */
+static int ThreadingAffinityTest08(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 0, 1, 2 ]\n"
+                         "      threads: 4\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->nb_threads == 4);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test interface-specific CPU set parsing
+ */
+static int ThreadingAffinityTest09(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 0, 1 ]\n"
+                         "      interface-specific-cpu-set:\n"
+                         "        - interface: \"eth0\"\n"
+                         "          cpu: [ 2, 3 ]\n"
+                         "          mode: \"exclusive\"\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->nb_children == 1);
+
+    ThreadsAffinityType *iface_taf = worker_taf->children[0];
+    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
+    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&iface_taf->cpu_set) == 2);
+    FAIL_IF_NOT(iface_taf->mode_flag == EXCLUSIVE_AFFINITY);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test multiple interface-specific CPU sets
+ */
+static int ThreadingAffinityTest10(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    receive-cpu-set:\n"
+                         "      cpu: [ 0 ]\n"
+                         "      interface-specific-cpu-set:\n"
+                         "        - interface: \"eth0\"\n"
+                         "          cpu: [ 1, 2 ]\n"
+                         "        - interface: \"eth1\"\n"
+                         "          cpu: [ 3, 4 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *receive_taf = &thread_affinity[RECEIVE_CPU_SET];
+    FAIL_IF_NOT(receive_taf->nb_children == 2);
+
+    bool eth0_found = false, eth1_found = false;
+
+    for (uint32_t i = 0; i < receive_taf->nb_children; i++) {
+        ThreadsAffinityType *iface_taf = receive_taf->children[i];
+        if (strcmp(iface_taf->name, "eth0") == 0) {
+            if (CPU_ISSET(1, &iface_taf->cpu_set) && CPU_ISSET(2, &iface_taf->cpu_set) &&
+                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
+                eth0_found = true;
+            }
+        } else if (strcmp(iface_taf->name, "eth1") == 0) {
+            if (CPU_ISSET(3, &iface_taf->cpu_set) && CPU_ISSET(4, &iface_taf->cpu_set) &&
+                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
+                eth1_found = true;
+            }
+        }
+    }
+
+    FAIL_IF_NOT(eth0_found && eth1_found);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test interface-specific priority settings
+ */
+static int ThreadingAffinityTest11(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 0 ]\n"
+                         "      interface-specific-cpu-set:\n"
+                         "        - interface: \"eth0\"\n"
+                         "          cpu: [ 1, 2, 3 ]\n"
+                         "          prio:\n"
+                         "            high: [ \"all\" ]\n"
+                         "            default: \"high\"\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->nb_children == 1);
+
+    ThreadsAffinityType *iface_taf = worker_taf->children[0];
+    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
+    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test complete configuration with all CPU sets
+ */
+static int ThreadingAffinityTest12(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    management-cpu-set:\n"
+                         "      cpu: [ 0 ]\n"
+                         "    receive-cpu-set:\n"
+                         "      cpu: [ 1 ]\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 2, 3 ]\n"
+                         "      interface-specific-cpu-set:\n"
+                         "        - interface: \"eth0\"\n"
+                         "          cpu: [ \"5-7\" ]\n"
+                         "          prio:\n"
+                         "            high: [ \"all\" ]\n"
+                         "            default: \"high\"\n"
+                         "    verdict-cpu-set:\n"
+                         "      cpu: [ 4 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    FAIL_IF_NOT(CPU_ISSET(0, &thread_affinity[MANAGEMENT_CPU_SET].cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 1);
+    FAIL_IF_NOT(CPU_ISSET(1, &thread_affinity[RECEIVE_CPU_SET].cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[RECEIVE_CPU_SET].cpu_set) == 1);
+    FAIL_IF_NOT(CPU_ISSET(4, &thread_affinity[VERDICT_CPU_SET].cpu_set));
+    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[VERDICT_CPU_SET].cpu_set) == 1);
+    FAIL_IF_NOT(CPU_ISSET(2, &thread_affinity[WORKER_CPU_SET].cpu_set));
+    FAIL_IF_NOT(CPU_ISSET(3, &thread_affinity[WORKER_CPU_SET].cpu_set));
+
+    FAIL_IF_NOT(thread_affinity[WORKER_CPU_SET].nb_children == 1);
+    ThreadsAffinityType *iface_taf = thread_affinity[WORKER_CPU_SET].children[0];
+    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
+    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
+    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
+    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == 2);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test error handling for malformed CPU specification
+ */
+static int ThreadingAffinityTest13(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ \"invalid-cpu\" ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 0);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test empty configuration handling
+ */
+static int ThreadingAffinityTest14(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    FAIL_IF_NOT(
+            CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == UtilCpuGetNumProcessorsOnline());
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test CPU range parsing with invalid order (high-low)
+ * CPU ranges specified in reverse order should be handled
+ */
+static int ThreadingAffinityTest15(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        cpu: [ \"3-1\" ]\n"; // Invalid reverse range
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 0);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test invalid priority values in SetupDefaultPriority
+ * Invalid priority strings should return errors but pass
+ */
+static int ThreadingAffinityTest16(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        prio:\n"
+                         "          default: invalid_priority\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test invalid CPU affinity mode values
+ * Invalid mode strings should return errors but pass
+ */
+static int ThreadingAffinityTest17(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        mode: invalid_mode\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test invalid thread count values
+ * Invalid thread counts
+ */
+static int ThreadingAffinityTest18(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        threads: 0\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test CPU specification with negative numbers
+ * Negative CPU numbers should be rejected
+ */
+static int ThreadingAffinityTest19(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        cpu: [ -1 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test invalid thread count with non-numeric values
+ * Non-numeric thread counts should be handled
+ */
+static int ThreadingAffinityTest20(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        threads: invalid_number\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test extremely large CPU ranges
+ * Very large CPU range specifications should be handled
+ */
+static int ThreadingAffinityTest21(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - management-cpu-set:\n"
+                         "        cpu: [ 0-99999 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test deeply nested interface configurations
+ * Prevent infinite loops in configuration parsing
+ */
+static int ThreadingAffinityTest22(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      interface-specific-cpu-set:\n"
+                         "        - interface: eth0\n"
+                         "          cpu: [ 1 ]\n"
+                         "          interface-specific-cpu-set:\n" // Nested interface-specific
+                         "            - interface: eth1\n"
+                         "              cpu: [ 2 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->nb_children == 1);
+    ThreadsAffinityType *iface_taf = worker_taf->children[0];
+    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
+    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->cpu_set));
+    FAIL_IF_NOT(iface_taf->nb_children == 0);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test GetAffinityTypeForNameAndIface with NULL and empty string parameters
+ * Comprehensive NULL parameter testing
+ */
+static int ThreadingAffinityTest23(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    worker-cpu-set:\n"
+                         "      cpu: [ 1, 2, 3 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *result = GetAffinityTypeForNameAndIface(NULL, "eth0");
+    FAIL_IF_NOT(result == NULL);
+
+    result = GetAffinityTypeForNameAndIface("", "eth0");
+    FAIL_IF_NOT(result == NULL);
+
+    result = GetAffinityTypeForNameAndIface("worker-cpu-set", NULL);
+    FAIL_IF(result == NULL);
+    FAIL_IF_NOT(strcmp(result->name, "worker-cpu-set") == 0);
+
+    result = GetAffinityTypeForNameAndIface("worker-cpu-set", "");
+    FAIL_IF_NOT(result == NULL); // Returns NULL as no child with an empty name exists
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test interface-specific configuration with missing interface field
+ * Interface-specific configs with malformed structure
+ */
+static int ThreadingAffinityTest24(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    - worker-cpu-set:\n"
+                         "        interface-specific-cpu-set:\n"
+                         "          - cpu: [ 1 ]\n" // Missing interface field
+                         "            mode: exclusive\n"
+                         "          - interface_name: eth0\n" // Wrong field name
+                         "            cpu: [ 2 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    FAIL_IF_NOT(worker_taf->nb_children == 0);
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+/**
+ * \brief Test AllocAndInitAffinityType with multiple allocations to test realloc paths
+ * Test dynamic array reallocation in parent-child relationships
+ */
+static int ThreadingAffinityTest25(void)
+{
+    ResetAffinityForTest();
+
+    ThreadsAffinityType *parent = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", NULL);
+    FAIL_IF(parent == NULL);
+
+    ThreadsAffinityType *child1 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface1");
+    ThreadsAffinityType *child2 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
+    ThreadsAffinityType *child3 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface3");
+    ThreadsAffinityType *child4 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface4");
+
+    FAIL_IF_NOT(child1 && child2 && child3 && child4);
+    FAIL_IF_NOT(parent->nb_children == 4);
+
+    ThreadsAffinityType *found = FindAffinityByInterface(parent, "iface2");
+    FAIL_IF_NOT(found == child2);
+
+    found = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
+    FAIL_IF_NOT(found == child2);
+
+    PASS;
+}
+
+/**
+ * \brief Test AffinityGetYamlPath with very long name
+ * Path building with long string lengths to test for buffer overflows
+ */
+static int ThreadingAffinityTest26(void)
+{
+    ResetAffinityForTest();
+
+    ThreadsAffinityType test_taf;
+    memset(&test_taf, 0, sizeof(test_taf));
+
+    char long_name[1024];
+    memset(long_name, 'a', sizeof(long_name) - 1);
+    long_name[sizeof(long_name) - 1] = '\0';
+    test_taf.name = long_name;
+
+    char *path = AffinityGetYamlPath(&test_taf);
+    FAIL_IF_NOT(path == NULL); // overflows the internal buffer and return NULL
+
+    path = AffinityGetYamlPath(NULL); // returns root path
+    FAIL_IF(path == NULL || strcmp(path, "threading.cpu-affinity") != 0);
+
+    PASS;
+}
+
+/**
+ * \brief Test mixed format configurations in same file
+ * Combination of new and deprecated formats
+ */
+static int ThreadingAffinityTest27(void)
+{
+    SCConfCreateContextBackup();
+    SCConfInit();
+    ResetAffinityForTest();
+
+    const char *config = "%YAML 1.1\n"
+                         "---\n"
+                         "threading:\n"
+                         "  cpu-affinity:\n"
+                         "    management-cpu-set:\n" // New format
+                         "      cpu: [ 0 ]\n"
+                         "    - worker-cpu-set:\n" // Deprecated format
+                         "        cpu: [ 1, 2 ]\n";
+
+    SCConfYamlLoadString(config, strlen(config));
+    AffinitySetupLoadFromConfig();
+
+    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
+    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
+    // The first format should be picked-up and the other should be ignored
+    // For ignored formats, CPU_SET is initliazed as all cores
+    FAIL_IF(CPU_COUNT(&mgmt_taf->cpu_set) != 1 ||
+            CPU_COUNT(&worker_taf->cpu_set) != UtilCpuGetNumProcessorsOnline());
+
+    SCConfRestoreContextBackup();
+    PASS;
+}
+
+#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
+
+/**
+ * \brief Register all threading affinity unit tests
+ */
+void ThreadingAffinityRegisterTests(void)
+{
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
+    UtRegisterTest("ThreadingAffinityTest01", ThreadingAffinityTest01);
+    UtRegisterTest("ThreadingAffinityTest02", ThreadingAffinityTest02);
+    UtRegisterTest("ThreadingAffinityTest03", ThreadingAffinityTest03);
+    UtRegisterTest("ThreadingAffinityTest04", ThreadingAffinityTest04);
+    UtRegisterTest("ThreadingAffinityTest05", ThreadingAffinityTest05);
+    UtRegisterTest("ThreadingAffinityTest06", ThreadingAffinityTest06);
+    UtRegisterTest("ThreadingAffinityTest07", ThreadingAffinityTest07);
+    UtRegisterTest("ThreadingAffinityTest08", ThreadingAffinityTest08);
+    UtRegisterTest("ThreadingAffinityTest09", ThreadingAffinityTest09);
+    UtRegisterTest("ThreadingAffinityTest10", ThreadingAffinityTest10);
+    UtRegisterTest("ThreadingAffinityTest11", ThreadingAffinityTest11);
+    UtRegisterTest("ThreadingAffinityTest12", ThreadingAffinityTest12);
+    UtRegisterTest("ThreadingAffinityTest13", ThreadingAffinityTest13);
+    UtRegisterTest("ThreadingAffinityTest14", ThreadingAffinityTest14);
+    UtRegisterTest("ThreadingAffinityTest15", ThreadingAffinityTest15);
+    UtRegisterTest("ThreadingAffinityTest16", ThreadingAffinityTest16);
+    UtRegisterTest("ThreadingAffinityTest17", ThreadingAffinityTest17);
+    UtRegisterTest("ThreadingAffinityTest18", ThreadingAffinityTest18);
+    UtRegisterTest("ThreadingAffinityTest19", ThreadingAffinityTest19);
+    UtRegisterTest("ThreadingAffinityTest20", ThreadingAffinityTest20);
+    UtRegisterTest("ThreadingAffinityTest21", ThreadingAffinityTest21);
+    UtRegisterTest("ThreadingAffinityTest22", ThreadingAffinityTest22);
+    UtRegisterTest("ThreadingAffinityTest23", ThreadingAffinityTest23);
+    UtRegisterTest("ThreadingAffinityTest24", ThreadingAffinityTest24);
+    UtRegisterTest("ThreadingAffinityTest25", ThreadingAffinityTest25);
+    UtRegisterTest("ThreadingAffinityTest26", ThreadingAffinityTest26);
+    UtRegisterTest("ThreadingAffinityTest27", ThreadingAffinityTest27);
+#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
+}
+
+#endif /* UNITTESTS */