#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] = {
{
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 */