diff --git a/src/runmode-unittests.c b/src/runmode-unittests.c index f11cce5fb3..d4e57e9fe6 100644 --- a/src/runmode-unittests.c +++ b/src/runmode-unittests.c @@ -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(); diff --git a/src/util-affinity.c b/src/util-affinity.c index 501f335732..8707aa5fdc 100644 --- a/src/util-affinity.c +++ b/src/util-affinity.c @@ -27,11 +27,13 @@ #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 */