From 0981056ed6ab5281f05666085e4cca683f60f840 Mon Sep 17 00:00:00 2001 From: varusing Date: Thu, 29 Jan 2026 12:29:48 +0530 Subject: [PATCH 1/8] Resolve Build Warnings Signed-off-by: varusing --- client/Comm/Socket/SocketClient.cpp | 3 ++- extensions/Include/Extensions.h | 3 +-- resource-tuner/core/Server/Include/RestuneListener.h | 2 +- resource-tuner/core/Server/RestuneListener.cpp | 3 ++- resource-tuner/core/TargetRegistry.cpp | 4 ++-- resource-tuner/init/RestuneParser.cpp | 4 ++-- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/client/Comm/Socket/SocketClient.cpp b/client/Comm/Socket/SocketClient.cpp index fc582f43a..42954dc6b 100644 --- a/client/Comm/Socket/SocketClient.cpp +++ b/client/Comm/Socket/SocketClient.cpp @@ -20,7 +20,8 @@ int32_t SocketClient::initiateConnection() { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; - if(snprintf(addr.sun_path, UNIX_PATH_MAX, RESTUNE_SOCKET_PATH) >= UNIX_PATH_MAX) { + size_t written = snprintf(addr.sun_path, UNIX_PATH_MAX, RESTUNE_SOCKET_PATH); + if(written >= UNIX_PATH_MAX) { LOGE("RESTUNE_SOCKET_CLIENT", "Socket path too long"); close(this->sockFd); this->sockFd = -1; diff --git a/extensions/Include/Extensions.h b/extensions/Include/Extensions.h index dd96ea539..08d0c4eb4 100644 --- a/extensions/Include/Extensions.h +++ b/extensions/Include/Extensions.h @@ -71,8 +71,7 @@ class Extensions { static PostProcessingCallback getPostProcessingCallback(const std::string& identifier); }; -#define CONCAT_IMPL(a, b) a##b -#define CONCAT(a, b) CONCAT_IMPL(a, b) +#define CONCAT(a, b) a ## b /** * \def URM_REGISTER_RES_APPLIER_CB(resCode, resourceApplierCallback) diff --git a/resource-tuner/core/Server/Include/RestuneListener.h b/resource-tuner/core/Server/Include/RestuneListener.h index 8493ed668..3c21fbdd9 100644 --- a/resource-tuner/core/Server/Include/RestuneListener.h +++ b/resource-tuner/core/Server/Include/RestuneListener.h @@ -38,7 +38,7 @@ class SocketServer : public ServerEndpoint { ServerOnlineCheckCallback mServerOnlineCheckCb, MessageReceivedCallback mMessageRecvCb); - ~SocketServer(); + virtual ~SocketServer(); virtual int32_t ListenForClientRequests(); virtual int32_t closeConnection(); diff --git a/resource-tuner/core/Server/RestuneListener.cpp b/resource-tuner/core/Server/RestuneListener.cpp index dd2e06c9c..35b2b60b6 100644 --- a/resource-tuner/core/Server/RestuneListener.cpp +++ b/resource-tuner/core/Server/RestuneListener.cpp @@ -35,7 +35,8 @@ int32_t SocketServer::ListenForClientRequests() { struct sockaddr_un addr; memset(&addr, 0, sizeof(sockaddr_un)); addr.sun_family = AF_UNIX; - if(snprintf(addr.sun_path, UNIX_PATH_MAX, RESTUNE_SOCKET_PATH) >= UNIX_PATH_MAX) { + size_t written = snprintf(addr.sun_path, UNIX_PATH_MAX, RESTUNE_SOCKET_PATH); + if(written >= UNIX_PATH_MAX) { LOGE("RESTUNE_SOCKET_SERVER", "Socket path too long"); close(this->sockFd); this->sockFd = -1; diff --git a/resource-tuner/core/TargetRegistry.cpp b/resource-tuner/core/TargetRegistry.cpp index 128570a5a..be0d91561 100644 --- a/resource-tuner/core/TargetRegistry.cpp +++ b/resource-tuner/core/TargetRegistry.cpp @@ -157,7 +157,7 @@ void TargetRegistry::generatePolicyBasedMapping(std::vector& policy // Now, Create the Logical to Physical Mappings // Note the Clusters are arranged in increasing order of Capacities - for(int32_t i = 0; i < clusterConfigs.size(); i++) { + for(size_t i = 0; i < clusterConfigs.size(); i++) { this->mLogicalToPhysicalClusterMapping[i] = clusterConfigs[i].second.second->mPhysicalID; } } @@ -235,7 +235,7 @@ void TargetRegistry::getClusterIdBasedMapping() { // Now, Create the Logical to Physical Mappings // Note the Clusters are arranged in increasing order of Capacities - for(int32_t i = 0; i < clusterConfigs.size(); i++) { + for(size_t i = 0; i < clusterConfigs.size(); i++) { this->mLogicalToPhysicalClusterMapping[i] = clusterConfigs[i].second->mPhysicalID; } } diff --git a/resource-tuner/init/RestuneParser.cpp b/resource-tuner/init/RestuneParser.cpp index b61fa988b..70209ad27 100644 --- a/resource-tuner/init/RestuneParser.cpp +++ b/resource-tuner/init/RestuneParser.cpp @@ -696,7 +696,7 @@ ErrCode RestuneParser::parseSignalConfigYamlNode(const std::string& filePath, in rc = resourceBuilder->setNumValues(resValues.size()); } - for(int32_t idx = 0; idx < resValues.size(); idx++) { + for(size_t idx = 0; idx < resValues.size(); idx++) { if(RC_IS_OK(rc)) { rc = resourceBuilder->addValue(idx, resValues[idx]); } @@ -971,7 +971,7 @@ ErrCode RestuneParser::parsePerAppConfigYamlNode(const std::string& filePath) { return RC_YAML_INVALID_SYNTAX; } } - for(int32_t i = 0; i < itemArray.size(); i++) { + for(size_t i = 0; i < itemArray.size(); i++) { if(RC_IS_OK(rc)) { rc = appConfigBuider->addSigCode(i, itemArray[i]); if(RC_IS_NOTOK(rc)) { From dffd1f1e3d4348e635b0da5782156316565f4374 Mon Sep 17 00:00:00 2001 From: varusing Date: Thu, 19 Feb 2026 11:39:00 +0530 Subject: [PATCH 2/8] Debian build fix Signed-off-by: varusing --- debian/userspace-resource-manager.install | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/userspace-resource-manager.install b/debian/userspace-resource-manager.install index 023e5c522..53d2179f8 100644 --- a/debian/userspace-resource-manager.install +++ b/debian/userspace-resource-manager.install @@ -4,3 +4,6 @@ usr/lib/${DEB_HOST_MULTIARCH}/*.so* etc/urm/common/* usr/include/Urm/* usr/lib/systemd/system/urm.service +etc/urm/classifier/* +usr/lib/libContextualClassifier.so* +usr/lib/libml_inference_lib.so* \ No newline at end of file From 2b865d25d4e3376bf523fbf39d25611ae4403a56 Mon Sep 17 00:00:00 2001 From: varusing Date: Thu, 19 Feb 2026 11:41:47 +0530 Subject: [PATCH 3/8] Build urm fixes for debian build Signed-off-by: varusing --- debian/userspace-resource-manager.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/userspace-resource-manager.install b/debian/userspace-resource-manager.install index 53d2179f8..8ab3c7b28 100644 --- a/debian/userspace-resource-manager.install +++ b/debian/userspace-resource-manager.install @@ -6,4 +6,4 @@ usr/include/Urm/* usr/lib/systemd/system/urm.service etc/urm/classifier/* usr/lib/libContextualClassifier.so* -usr/lib/libml_inference_lib.so* \ No newline at end of file +usr/lib/libml_inference_lib.so* From 9882122c0f5f5c315b1496e40fe36c295283201f Mon Sep 17 00:00:00 2001 From: varusing Date: Thu, 26 Feb 2026 16:35:58 +0530 Subject: [PATCH 4/8] Add ML-based contextual classification tests with YAML config and integration tests Signed-off-by: varusing --- contextual-classifier/CMakeLists.txt | 1 + tests/CMakeLists.txt | 38 +- tests/Component/ContextClassificationTest.cpp | 406 ++++++++++++++++++ .../Configs/ClassificationAppPredConfig.yaml | 134 ++++++ tests/Integration/CCIntegrationTest.cpp | 46 ++ tests/README.md | 112 +---- 6 files changed, 613 insertions(+), 124 deletions(-) create mode 100644 tests/Component/ContextClassificationTest.cpp create mode 100644 tests/Configs/ClassificationAppPredConfig.yaml create mode 100644 tests/Integration/CCIntegrationTest.cpp diff --git a/contextual-classifier/CMakeLists.txt b/contextual-classifier/CMakeLists.txt index 9a2cfad4f..efdb1cc4b 100644 --- a/contextual-classifier/CMakeLists.txt +++ b/contextual-classifier/CMakeLists.txt @@ -56,6 +56,7 @@ if(FLORET_FOUND) target_link_libraries(ml_inference_lib PRIVATE ${FLORET_LIBRARIES}) target_link_libraries(ContextualClassifier PRIVATE ml_inference_lib) + target_link_libraries(ContextualClassifier PRIVATE RestuneCore) target_include_directories(ml_inference_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Include) target_include_directories(ml_inference_lib PRIVATE ${FLORET_INCLUDE_DIRS}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4977b650d..8af16afec 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,7 +30,7 @@ if(BUILD_TESTS) install(TARGETS UrmTestPlugin DESTINATION ${CMAKE_INSTALL_LIBDIR}/urm) add_executable( - RestuneComponentTests + UrmComponentTests ${CMAKE_CURRENT_SOURCE_DIR}/Component/MemoryPoolTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Component/ClientDataManagerTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Component/ParserTests.cpp @@ -44,21 +44,31 @@ if(BUILD_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/Component/DeviceInfoTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Component/CocoTableTests.cpp # ${CMAKE_CURRENT_SOURCE_DIR}/Component/RequestMapTests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Component/Trigger.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/Component/Trigger.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Component/ContextClassificationTest.cpp) - target_link_libraries(RestuneComponentTests PUBLIC UrmAuxUtils - UrmExtAPIs - RestuneCore - RestuneTestUtils) + target_link_libraries(UrmComponentTests PUBLIC UrmAuxUtils + UrmExtAPIs + RestuneCore + ContextualClassifier + ml_inference_lib + RestuneTestUtils + ${LIBYAML_LIBRARIES}) + target_include_directories(UrmComponentTests PRIVATE ${LIBYAML_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/contextual-classifier/Include + ${CMAKE_SOURCE_DIR}/resource-tuner/Include + ${CMAKE_SOURCE_DIR}/common/Include) - install(TARGETS RestuneComponentTests DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(TARGETS UrmComponentTests DESTINATION ${CMAKE_INSTALL_BINDIR}) - add_executable(RestuneIntegrationTests Integration/IntegrationTests.cpp) - target_link_libraries(RestuneIntegrationTests PUBLIC UrmAuxUtils - UrmClient - RestuneTestUtils - ${LIBYAML_LIBRARIES}) - target_include_directories(RestuneIntegrationTests PRIVATE ${LIBYAML_INCLUDE_DIRS}) - install(TARGETS RestuneIntegrationTests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + add_executable(UrmIntegrationTests Integration/IntegrationTests.cpp Integration/CCIntegrationTest.cpp) + add_definitions(-DUSE_FLORET=1) + + target_link_libraries(UrmIntegrationTests PUBLIC UrmAuxUtils + UrmClient + RestuneTestUtils + ${LIBYAML_LIBRARIES}) + target_include_directories(UrmIntegrationTests PRIVATE ${LIBYAML_INCLUDE_DIRS}) + install(TARGETS UrmIntegrationTests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/tests/Component/ContextClassificationTest.cpp b/tests/Component/ContextClassificationTest.cpp new file mode 100644 index 000000000..e7ec4f3fa --- /dev/null +++ b/tests/Component/ContextClassificationTest.cpp @@ -0,0 +1,406 @@ +// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +// SPDX-License-Identifier: BSD-3-Clause-Clear + +/** + * These tests validate the machine learning-based application classification + * functionality. The test suite spawns various applications and verifies that + * the ML model correctly classifies them into predefined categories: + * - APP (General applications) + * - BROWSER (Web browsers) + * - GAME (Gaming applications) + * - MULTIMEDIA (Media players, editors) + * + * Test configuration is loaded from a YAML file containing application metadata + * including executables, arguments, and expected classification labels. + * + * The tests mirror real-world usage where applications are launched and the + * classifier must identify their category based on runtime characteristics. + */ + +#include "ErrCodes.h" +#include "UrmPlatformAL.h" +#include "Utils.h" +#include "TestUtils.h" +#include "URMTests.h" +#include "MLInference.h" +#include +#include +#include +#include +#include + +// Test configuration paths and identifiers +#define TEST_CLASS "COMPONENT" +#define CLASSIFIER_CONFIGS_DIR "/etc/urm/classifier/" +#define TEST_CONFIG_PATH "/etc/urm/tests/configs/ClassificationAppPredConfig.yaml" + +static const std::string FT_MODEL_PATH = CLASSIFIER_CONFIGS_DIR "floret_model_supervised.bin"; + +/** + * Contains all metadata needed to spawn an application and validate + * its classification against expected results. Each test application + * is defined with: + * - Execution parameters (binary path, arguments) + * - Expected classification results (category name and numeric label) + * - Test metadata (description, startup delay) + */ +struct TestAppConfig { + std::string name; ///< Application name (used in process spawn) + std::string executable; ///< Full path to executable binary + std::vector args; ///< Command-line arguments for application launch + std::string expectedCategory; ///< Human-readable expected category (e.g., "CC_MULTIMEDIA") + int32_t expectedLabel; ///< Numeric label for ML model validation (1=APP, 2=BROWSER, 3=GAME, 4=MULTIMEDIA) + std::string description; ///< Test case description for logging + int startupDelayMs; ///< Milliseconds to wait after spawn before classification +}; + +/** + * Implements a lightweight YAML parser specifically designed for the test + * configuration format. Extracts application metadata including executables, + * arguments, and expected classification labels. + * + * The parser handles the following YAML structure: + * + * test_applications: + * - name: "app_name" + * executable: "/path/to/binary" + * args: ["arg1", "arg2"] + * expected_category: "CC_CATEGORY" + * expected_label: N + * description: "Description" + * startup_delay_ms: NNNN + */ +std::vector loadAppConfigs(const std::string& configPath) { + std::vector apps; + std::ifstream file(configPath); + + if (!file.is_open()) { + return apps; + } + + std::string line; + TestAppConfig currentApp; + bool inApp = false; + + // Parse line by line + while (std::getline(file, line)) { + // Trim leading whitespace + size_t first = line.find_first_not_of(" \t"); + if (first == std::string::npos) continue; + line = line.substr(first); + + // Skip empty lines and comments + if (line.empty() || line[0] == '#') continue; + + // Detect start of new application entry + if (line.find("- name:") != std::string::npos) { + if (inApp) apps.push_back(currentApp); + currentApp = TestAppConfig(); + size_t pos = line.find('"'); + if (pos != std::string::npos) { + size_t end = line.find('"', pos + 1); + currentApp.name = line.substr(pos + 1, end - pos - 1); + } + inApp = true; + } else if (inApp) { + // Parse application properties + if (line.find("executable:") != std::string::npos) { + size_t pos = line.find('"'); + if (pos != std::string::npos) { + size_t end = line.find('"', pos + 1); + currentApp.executable = line.substr(pos + 1, end - pos - 1); + } + } else if (line.find("args:") != std::string::npos) { + // Parse argument array [arg1, arg2, ...] + size_t start = line.find('['); + size_t end = line.find(']'); + if (start != std::string::npos && end != std::string::npos) { + std::string argsStr = line.substr(start + 1, end - start - 1); + std::stringstream ss(argsStr); + std::string arg; + while (std::getline(ss, arg, ',')) { + size_t pos = arg.find('"'); + if (pos != std::string::npos) { + size_t end = arg.find('"', pos + 1); + std::string trimmed = arg.substr(pos + 1, end - pos - 1); + currentApp.args.push_back(trimmed); + } + } + } + } else if (line.find("expected_category:") != std::string::npos) { + size_t pos = line.find('"'); + if (pos != std::string::npos) { + size_t end = line.find('"', pos + 1); + currentApp.expectedCategory = line.substr(pos + 1, end - pos - 1); + } + } else if (line.find("expected_label:") != std::string::npos) { + size_t pos = line.find(':'); + if (pos != std::string::npos) { + currentApp.expectedLabel = std::stoi(line.substr(pos + 1)); + } + } else if (line.find("description:") != std::string::npos) { + size_t pos = line.find('"'); + if (pos != std::string::npos) { + size_t end = line.find('"', pos + 1); + currentApp.description = line.substr(pos + 1, end - pos - 1); + } + } else if (line.find("startup_delay_ms:") != std::string::npos) { + size_t pos = line.find(':'); + if (pos != std::string::npos) { + currentApp.startupDelayMs = std::stoi(line.substr(pos + 1)); + } + } + } + } + + // Add final application if present + if (inApp) apps.push_back(currentApp); + return apps; +} + + +/* + * Description: + * This test suite validates the ML-based contextual classification system. + * The classifier uses a Floret-based machine learning model to categorize + * running applications into predefined classes based on their runtime behavior. + * + * Test Coverage: + * - Model Loading and Initialization + * - Application Spawning and Process Management + * - ML Inference Execution + * - Classification Accuracy Validation + * - Resource Cleanup and Reset + * + * Classification Categories: + * - CC_APP (1): General applications (editors, productivity tools) + * - CC_BROWSER (2): Web browsers (Chrome, Firefox, etc.) + * - CC_GAME (3): Gaming applications + * - CC_MULTIMEDIA (4): Media players and editors (VLC, MPV, etc.) + * + * Test Flow: + * 1. Verify ML model file exists and is accessible + * 2. Load test application configurations from YAML + * 3. Initialize ML inference engine (Floret model) + * 4. For each configured application: + * a. Verify executable exists + * b. Spawn process with configured arguments + * c. Wait for application startup (configurable delay) + * d. Run ML classification on process + * e. Compare predicted label with expected label + * f. Terminate process and collect results + * 5. Generate summary report with pass/fail/skip counts + * 6. List all failed and skipped applications for debugging + * + * Notes: + * - Requires USE_FLORET to be defined at compile time + * - Applications that fail to spawn are marked as SKIPPED, not FAILED + * - Test fails if no applications pass or if any classification is incorrect + * - All spawned processes are properly cleaned up (SIGTERM + waitpid) + */ + +/** + * API under test: MLInference::Classify + * - Validates that the ML model correctly classifies various applications + * - Tests multiple application categories (APP, BROWSER, GAME, MULTIMEDIA) + * - Verifies classification accuracy against expected labels + * - Handles missing executables gracefully (marked as SKIPPED) + * - Provides detailed logging for debugging classification failures + * Cross-Reference: Comprehensive application classification validation + */ +URM_TEST(TestApplicationClassification, { + const std::string modelPath = FT_MODEL_PATH; + + // Wait for URM service to fully initialize and settle + std::this_thread::sleep_for(std::chrono::milliseconds(1800)); + + // Verify ML model file is present and accessible + int64_t modelExists = AuxRoutines::fileExists(modelPath); + if (modelExists <= 0) { + std::cout << LOG_BASE << "Model not found, skipping test" << std::endl; + E_ASSERT(false); + return; + } + + std::cout << LOG_BASE << "Testing application classification..." << std::endl; + + // Verify Floret support is enabled at compile time + #ifndef USE_FLORET + std::cout << LOG_BASE << "ERROR: USE_FLORET not defined!" << std::endl; + std::cout << LOG_BASE << "Cannot create MLInference object" << std::endl; + std::cout << LOG_BASE << "Rebuild with: cmake -DUSE_FLORET=ON .." << std::endl; + E_ASSERT(false); + return; + #endif + + // Load test configurations from YAML file + std::vector apps = loadAppConfigs(TEST_CONFIG_PATH); + + if (apps.empty()) { + std::cout << LOG_BASE << "ERROR: No applications found in config file" << std::endl; + std::cout << LOG_BASE << "Config path: " << TEST_CONFIG_PATH << std::endl; + E_ASSERT(false); + return; + } + + std::cout << LOG_BASE << "Found " << apps.size() << " application(s) to test" << std::endl; + + // Initialize ML inference engine (Floret-based model) + Inference* inference = nullptr; + #ifdef USE_FLORET + inference = new MLInference(FT_MODEL_PATH); + #else + inference = new Inference(modelPath); + #endif + + E_ASSERT((inference != nullptr)); + std::cout << LOG_BASE << "Inference created successfully" << std::endl; + + // Test result counters + int passed = 0; + int failed = 0; + int skipped = 0; + + // Track failed and skipped applications for detailed reporting + std::vector failedApps; + std::vector skippedApps; + + // Execute classification test for each configured application + for (const auto& app : apps) { + std::cout << LOG_BASE << "\n=== Testing: " << app.name << " ===" << std::endl; + std::cout << LOG_BASE << "Description: " << app.description << std::endl; + std::cout << LOG_BASE << "Expected: " << app.expectedCategory + << " (label=" << app.expectedLabel << ")" << std::endl; + + // Verify executable exists before attempting spawn + if (!AuxRoutines::fileExists(app.executable)) { + std::cout << LOG_BASE << "Executable not found: " << app.executable << std::endl; + std::cout << LOG_BASE << "Status: SKIPPED" << std::endl; + skipped++; + skippedApps.push_back(app.name); + continue; + } + + // Fork and spawn application process + pid_t pid = fork(); + if (pid == 0) { + // Child process: redirect output to suppress application noise + // This prevents terminal clutter from application stderr/stdout + int devNull = open("/dev/null", O_WRONLY); + if (devNull != -1) { + dup2(devNull, STDERR_FILENO); + dup2(devNull, STDOUT_FILENO); + close(devNull); + } + + // Build argument vector for execv + std::vector argv; + argv.push_back(app.name.c_str()); + for (const auto& arg : app.args) { + argv.push_back(arg.c_str()); + } + argv.push_back(nullptr); + + // Execute application - if successful, this replaces the child process + execv(app.executable.c_str(), const_cast(argv.data())); + + // If execv returns, it failed + exit(EXIT_FAILURE); + + } else if (pid > 0) { + // Parent process: wait for application to start and stabilize + std::this_thread::sleep_for(std::chrono::milliseconds(app.startupDelayMs)); + + // Verify process is still alive (didn't crash during startup) + char commPath[256]; + snprintf(commPath, sizeof(commPath), "/proc/%d/comm", pid); + + if (!AuxRoutines::fileExists(commPath)) { + std::cout << LOG_BASE << "Process died after spawn" << std::endl; + std::cout << LOG_BASE << "Status: SKIPPED" << std::endl; + skipped++; + skippedApps.push_back(app.name); + continue; + } + + // Read process name from /proc for logging + std::string comm = AuxRoutines::readFromFile(commPath); + if (!comm.empty() && comm.back() == '\n') { + comm.pop_back(); + } + + std::cout << LOG_BASE << "Process: " << comm << " (PID: " << pid << ")" << std::endl; + + // Call ML model inference to classify the running process + std::cout << LOG_BASE << "Running model inference..." << std::endl; + CC_TYPE predictedLabel = inference->Classify(pid); + + std::cout << LOG_BASE << "Predicted label: " << predictedLabel << std::endl; + + // Decode numeric prediction to human-readable category name + const char* categoryName = "UNKNOWN"; + switch(predictedLabel) { + case CC_APP: categoryName = "APP"; break; + case CC_BROWSER: categoryName = "BROWSER"; break; + case CC_GAME: categoryName = "GAME"; break; + case CC_MULTIMEDIA: categoryName = "MULTIMEDIA"; break; + default: categoryName = "APP"; + } + + std::cout << LOG_BASE << "Category: " << categoryName << std::endl; + + // Compare predicted label with expected label + bool correct = (predictedLabel == app.expectedLabel); + std::cout << LOG_BASE << "Match: " << (correct ? "YES" : "NO") << std::endl; + + // Cleanup: terminate spawned process + // Use SIGTERM for graceful shutdown, then wait for process to exit + kill(pid, SIGTERM); + waitpid(pid, nullptr, 0); + + // Update test statistics + if (correct) { + std::cout << LOG_BASE << "Status: ✓ PASSED" << std::endl; + passed++; + } else { + std::cout << LOG_BASE << "Status: ✗ FAILED" << std::endl; + failed++; + failedApps.push_back(app.name); + } + } + } + + // Cleanup inference engine + delete inference; + + // Generate comprehensive test summary + std::cout << LOG_BASE << "\n\n===== TestApplicationClassification Summary ====" << std::endl; + std::cout << LOG_BASE << "Total: " << apps.size() << std::endl; + std::cout << LOG_BASE << "Passed: " << passed << std::endl; + std::cout << LOG_BASE << "Failed: " << failed << std::endl; + std::cout << LOG_BASE << "Skipped: " << skipped << std::endl; + + // Display detailed list of failed applications for debugging + if (!failedApps.empty()) { + std::cout << LOG_BASE << "\n\n==================== Failed Applications ===" << std::endl; + for (const auto& appName : failedApps) { + std::cout << LOG_BASE << " - " << appName << std::endl; + } + } + + // Display detailed list of skipped applications (missing executables or crashed) + if (!skippedApps.empty()) { + std::cout << LOG_BASE << "\n\n=================== Skipped Applications ===" << std::endl; + for (const auto& appName : skippedApps) { + std::cout << LOG_BASE << " - " << appName << std::endl; + } + } + + // Assert test success criteria: + // - At least one application must pass (validates model is working) + // - No applications should fail (all classifications must be correct) + E_ASSERT((passed > 0)); + E_ASSERT((failed == 0)); + + std::cout << LOG_BASE << "Application classification tests PASSED" << std::endl; +}) diff --git a/tests/Configs/ClassificationAppPredConfig.yaml b/tests/Configs/ClassificationAppPredConfig.yaml new file mode 100644 index 000000000..e6f839d52 --- /dev/null +++ b/tests/Configs/ClassificationAppPredConfig.yaml @@ -0,0 +1,134 @@ +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +# Classification App Prediction Configuration +# +# This file defines test applications for validating the ML-based classifier. +# Each app is spawned and classified to verify the model works correctly. +# +# Classification Labels: +# APP (1) - General applications, editors, productivity tools +# BROWSER (2) - Web browsers +# GAME (3) - Games +# MULTIMEDIA (4) - Media players and editors + +test_applications: + # MULTIMEDIA Applications + + - name: "vlc" + executable: "/usr/bin/vlc" + args: ["--intf", "dummy"] + expected_category: "CC_MULTIMEDIA" + expected_label: 4 + description: "VLC media player" + startup_delay_ms: 3000 + + - name: "mpv" + executable: "/usr/bin/mpv" + args: ["--idle", "--no-video"] + expected_category: "CC_MULTIMEDIA" + expected_label: 4 + description: "MPV media player" + startup_delay_ms: 2000 + + - name: "audacity" + executable: "/usr/bin/audacity" + args: [] + expected_category: "CC_MULTIMEDIA" + expected_label: 4 + description: "Audacity audio editor" + startup_delay_ms: 3000 + + - name: "gstreamer" + executable: "/usr/bin/gst-launch-1.0" + args: ["videotestsrc", "pattern=smpte", "!", "videoconvert", "!", "autovideosink"] + expected_category: "CC_MULTIMEDIA" + expected_label: 4 + description: "GStreamer multimedia framework with GUI display" + startup_delay_ms: 3000 + + # BROWSER Applications + + - name: "firefox" + executable: "/usr/bin/firefox" + args: ["--headless", "--no-remote"] + expected_category: "CC_BROWSER" + expected_label: 2 + description: "Firefox web browser" + startup_delay_ms: 4000 + + - name: "chrome" + executable: "/usr/bin/google-chrome" + args: ["--headless", "--disable-gpu", "--no-sandbox"] + expected_category: "CC_BROWSER" + expected_label: 2 + description: "Google Chrome" + startup_delay_ms: 4000 + + - name: "falkon" + executable: "/usr/bin/falkon" + args: ["--headless", "--disable-gpu", "--no-sandbox"] + expected_category: "CC_BROWSER" + expected_label: 2 + description: "Falkon web browser" + startup_delay_ms: 4000 + + - name: "chromium" + executable: "/usr/bin/chromium-browser" + args: ["--headless", "--disable-gpu", "--no-sandbox"] + expected_category: "CC_BROWSER" + expected_label: 2 + description: "Chromium web browser" + startup_delay_ms: 4000 + + # APP Applications + + - name: "gimp" + executable: "/usr/bin/gimp" + args: ["--no-interface"] + expected_category: "CC_APP" + expected_label: 1 + description: "GIMP image editor" + startup_delay_ms: 4000 + + - name: "libreoffice-writer" + executable: "/usr/bin/libreoffice" + args: ["--writer", "--headless"] + expected_category: "CC_APP" + expected_label: 1 + description: "LibreOffice Writer" + startup_delay_ms: 3500 + + - name: "blender" + executable: "/usr/bin/blender" + args: ["--background"] + expected_category: "CC_APP" + expected_label: 1 + description: "Blender 3D creation suite" + startup_delay_ms: 4500 + + - name: "vi" + executable: "/usr/bin/vi" + args: ["-e", "-s"] + expected_category: "CC_APP" + expected_label: 1 + description: "Vi text editor (ex mode)" + startup_delay_ms: 1500 + + # GAME Applications + + - name: "nsnake" + executable: "/snap/bin/nsnake" + args: [] + expected_category: "CC_GAME" + expected_label: 3 + description: "nSnake - Classic snake game for terminal" + startup_delay_ms: 2000 + + - name: "bastet" + executable: "/usr/games/bastet" + args: [] + expected_category: "CC_GAME" + expected_label: 3 + description: "Bastet - Bastard Tetris game" + startup_delay_ms: 2000 diff --git a/tests/Integration/CCIntegrationTest.cpp b/tests/Integration/CCIntegrationTest.cpp new file mode 100644 index 000000000..733064653 --- /dev/null +++ b/tests/Integration/CCIntegrationTest.cpp @@ -0,0 +1,46 @@ +// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +// SPDX-License-Identifier: BSD-3-Clause-Clear + +#include "ErrCodes.h" +#include "UrmPlatformAL.h" +#include "Utils.h" +#include "TestUtils.h" +#include "URMTests.h" + +// Test configuration and paths +#define TEST_CLASS "INTEGRATION" +#define CLASSIFIER_CONFIGS_DIR "/etc/urm/classifier/" + +// Path to the Floret supervised learning model binary +static const std::string FT_MODEL_PATH = CLASSIFIER_CONFIGS_DIR "floret_model_supervised.bin"; + + +/* + * Description: Verifies that the URM classifier model file is properly loaded and accessible + * at system startup. + */ +URM_TEST(TestModelLoads, { + const std::string modelPath = FT_MODEL_PATH; + + // Wait for URM service to complete initialization and settle + // This ensures all service components, including the classifier subsystem, + // are fully initialized before we attempt to access the model file + std::this_thread::sleep_for(std::chrono::milliseconds(1800)); + + // Verify model file exists and is accessible + // fileExists() returns file size if successful, -1 if file not found or inaccessible + int64_t modelExists = AuxRoutines::fileExists(modelPath); + + // Log the result for debugging + if (modelExists > 0) { + std::cout << LOG_BASE << "Model file found at: " << modelPath << std::endl; + std::cout << LOG_BASE << "Model file size: " << modelExists << " bytes" << std::endl; + } else { + std::cout << LOG_BASE << "ERROR: Model file not found or inaccessible" << std::endl; + std::cout << LOG_BASE << "Expected path: " << modelPath << std::endl; + std::cout << LOG_BASE << "Verify model deployment and file permissions" << std::endl; + } + + // Assert that model file exists and is accessible (size > 0) + E_ASSERT((modelExists > 0)); +}) diff --git a/tests/README.md b/tests/README.md index 7c19ddc24..ef8c37a44 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,125 +1,17 @@ -## New Enhancements -- **XFAIL & SKIP support**: - - Static: `MT_TEST_XFAIL`, `MT_TEST_SKIP`. - - Runtime: `MT_MARK_XFAIL(ctx, "reason" )`, `MT_SKIP(ctx, "reason")`. - - CLI: `--xfail-strict` (XPASS counts as failure). -- **Reports**: - - JSON, JUnit XML, Markdown. - - CLI flags: `--report-json`, `--report-junit`, `--report-md`. -- **Auto-report defaults**: - - Uses `TEST_REPORT_DIR` env var or current directory if no flags provided. - ---- - ## Quick Start -```cpp -#include "mini.hpp" - -MT_TEST(math, addition, "unit") { - MT_REQUIRE_EQ(ctx, 2 + 3, 5); -} -``` Build & run: ```bash mkdir build && cd build cmake .. -DBUILD_TESTS=ON make -./tests/RestuneComponentTests -./tests/RestuneIntegrationTests -./tests/RestuneUnitTests ``` --- ## Running Tests ```bash -# Filter by tag -./RestuneIntegrationTests --tag=cgroup - -# Generate reports manually -TEST_REPORT_DIR=./build/test-reports ./RestuneIntegrationTests -``` -Reports: -``` -build/test-reports/ - RestuneIntegrationTests.json - RestuneIntegrationTests.junit.xml - RestuneIntegrationTests.md -``` - ---- - -## CLI Options -- `--filter=` – name filter. -- `--tag=` – exact tag match. -- `--threads=N` – parallel execution. -- `--tap` / `--no-tap` – TAP output toggle. -- `--xfail-strict` – XPASS counts as failure. -- `--report-json`, `--report-junit`, `--report-md` – report paths. - ---- - -## Example Summary -``` -Summary: total=47, passed=29, failed=11, skipped=0, xfail=7, xpass=0, time=314566.983 ms -``` - ---- - -## Marker Options -You can mark tests as **expected failure (XFAIL)** or **skip** using these macros: - -### Static Markers -```cpp -MT_TEST_XFAIL(SuiteName, TestName, "tag", "reason") { - // Test logic (will be reported as XFAIL if it fails) -} - -MT_TEST_SKIP(SuiteName, TestName, "tag", "reason") { - // This test will be skipped entirely -} -``` - -### Runtime Markers -```cpp -MT_TEST(SuiteName, TestName, "tag") { - if (!condition) { - MT_SKIP(ctx, "Skipping due to condition"); - } - if (known_issue) { - MT_MARK_XFAIL(ctx, "Known issue: expected failure"); - } - // Normal test logic -} -``` - -Summary output includes `skipped`, `xfail`, and `xpass` counts. - ---- - -## CTest Integration -To run tests and generate reports automatically via CTest: - -### Example CMake snippet -```cmake -set(TEST_REPORT_DIR "${CMAKE_BINARY_DIR}/test-reports") -file(MAKE_DIRECTORY "${TEST_REPORT_DIR}") +./tests/UrmComponentTests +./tests/UrmIntegrationTests -add_test(NAME RestuneIntegrationTests_All - COMMAND RestuneIntegrationTests - --report-json=${TEST_REPORT_DIR}/integration_all.json - --report-junit=${TEST_REPORT_DIR}/integration_all.junit.xml - --report-md=${TEST_REPORT_DIR}/integration_all.md) - -set_tests_properties(RestuneIntegrationTests_All PROPERTIES - ENVIRONMENT "TEST_REPORT_DIR=${TEST_REPORT_DIR};NO_COLOR=") ``` - -Run all tests: -```bash -ctest -V -``` - -Reports will appear in `build/test-reports/`. - From 9bd69ca98f4cca33c19b0ce231c9daaf58ab8e1a Mon Sep 17 00:00:00 2001 From: varusing Date: Mon, 2 Mar 2026 22:14:48 +0530 Subject: [PATCH 5/8] Add ML-based contextual classification tests with YAML config and integration tests Signed-off-by: varusing --- tests/CMakeLists.txt | 16 +- tests/Component/ContextClassificationTest.cpp | 6 +- tests/Configs/PerApp.yaml | 6 + tests/Integration/CCIntegrationTest.cpp | 183 ++++++++++++++++++ 4 files changed, 203 insertions(+), 8 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8af16afec..c96e283cb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,21 +44,27 @@ if(BUILD_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/Component/DeviceInfoTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Component/CocoTableTests.cpp # ${CMAKE_CURRENT_SOURCE_DIR}/Component/RequestMapTests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Component/Trigger.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Component/ContextClassificationTest.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/Component/Trigger.cpp) target_link_libraries(UrmComponentTests PUBLIC UrmAuxUtils UrmExtAPIs RestuneCore - ContextualClassifier - ml_inference_lib RestuneTestUtils ${LIBYAML_LIBRARIES}) target_include_directories(UrmComponentTests PRIVATE ${LIBYAML_INCLUDE_DIRS} - ${CMAKE_SOURCE_DIR}/contextual-classifier/Include ${CMAKE_SOURCE_DIR}/resource-tuner/Include ${CMAKE_SOURCE_DIR}/common/Include) + if(BUILD_CLASSIFIER) + target_sources(UrmComponentTests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Component/ContextClassificationTest.cpp) + + target_link_libraries(UrmComponentTests PUBLIC ContextualClassifier + ml_inference_lib) + + target_include_directories(UrmComponentTests PRIVATE ${CMAKE_SOURCE_DIR}/contextual-classifier/Include) + endif() + install(TARGETS UrmComponentTests DESTINATION ${CMAKE_INSTALL_BINDIR}) add_executable(UrmIntegrationTests Integration/IntegrationTests.cpp Integration/CCIntegrationTest.cpp) diff --git a/tests/Component/ContextClassificationTest.cpp b/tests/Component/ContextClassificationTest.cpp index e7ec4f3fa..7855bc466 100644 --- a/tests/Component/ContextClassificationTest.cpp +++ b/tests/Component/ContextClassificationTest.cpp @@ -257,9 +257,9 @@ URM_TEST(TestApplicationClassification, { std::cout << LOG_BASE << "Inference created successfully" << std::endl; // Test result counters - int passed = 0; - int failed = 0; - int skipped = 0; + int32_t passed = 0; + int32_t failed = 0; + int32_t skipped = 0; // Track failed and skipped applications for detailed reporting std::vector failedApps; diff --git a/tests/Configs/PerApp.yaml b/tests/Configs/PerApp.yaml index 5b21335f0..996c319f9 100644 --- a/tests/Configs/PerApp.yaml +++ b/tests/Configs/PerApp.yaml @@ -11,3 +11,9 @@ PerAppConfigs: Threads: - {"chrome": "FOCUSED_CGROUP_IDENTIFIER"} Configurations: ["0x80034aab"] + + - App: "gst-launch-1.0" + Configurations: ["0x000d000a"] + + - App: "vi" + Configurations: ["0x000d000a"] diff --git a/tests/Integration/CCIntegrationTest.cpp b/tests/Integration/CCIntegrationTest.cpp index 733064653..b939f8603 100644 --- a/tests/Integration/CCIntegrationTest.cpp +++ b/tests/Integration/CCIntegrationTest.cpp @@ -6,6 +6,7 @@ #include "Utils.h" #include "TestUtils.h" #include "URMTests.h" +#include "UrmAPIs.h" // Test configuration and paths #define TEST_CLASS "INTEGRATION" @@ -44,3 +45,185 @@ URM_TEST(TestModelLoads, { // Assert that model file exists and is accessible (size > 0) E_ASSERT((modelExists > 0)); }) + +/** + * API under test: Tune / Untune + * - Spawns GStreamer and verifies that the per-app config + * correctly tunes the resource node when GStreamer is running + * - Verifies resource value is set when GStreamer is active + * - Verifies resource value resets once the request expires + */ +URM_TEST(TestGstreamerPerAppConfigValidated, { + const std::string modelPath = FT_MODEL_PATH; + + // Wait for service to settle + std::this_thread::sleep_for(std::chrono::milliseconds(1800)); + + // Verify model file exists + int64_t modelExists = AuxRoutines::fileExists(modelPath); + if (modelExists <= 0) { + std::cout << LOG_BASE << "Model not found, skipping test" << std::endl; + E_ASSERT(false); + return; + } + + std::string testResourceName = "/etc/urm/tests/nodes/target_test_resource1.txt"; + int32_t testResourceOriginalValue = 240; + + std::string value; + int32_t originalValue, newValue; + + // Verify original resource value + value = AuxRoutines::readFromFile(testResourceName); + originalValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Original Value: " << originalValue << std::endl; + E_ASSERT((originalValue == testResourceOriginalValue)); + + // Verify executable exists before attempting spawn + if (!AuxRoutines::fileExists("/usr/bin/gst-launch-1.0")) { + std::cout << LOG_BASE << "Executable not found: /usr/bin/gst-launch-1.0" << std::endl; + std::cout << LOG_BASE << "Status: SKIPPED" << std::endl; + return; + } + + // Spawn GStreamer + pid_t pid = fork(); + if (pid == 0) { + // Child process: redirect output to suppress application noise + int devNull = open("/dev/null", O_WRONLY); + if (devNull != -1) { + dup2(devNull, STDERR_FILENO); + dup2(devNull, STDOUT_FILENO); + close(devNull); + } + execl("/usr/bin/gst-launch-1.0", "gst-launch-1.0", + "videotestsrc", "pattern=smpte", "!", + "videoconvert", "!", "autovideosink", nullptr); + exit(EXIT_FAILURE); + + } else if (pid > 0) { + // Wait for GStreamer to start + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + + // Verify process is alive + char commPath[256]; + snprintf(commPath, sizeof(commPath), "/proc/%d/comm", pid); + E_ASSERT(AuxRoutines::fileExists(commPath)); + + // Read and log process name + std::string comm = AuxRoutines::readFromFile(commPath); + if (!comm.empty() && comm.back() == '\n') { + comm.pop_back(); + } + std::cout << LOG_BASE << "Process: " << comm << " (PID: " << pid << ")" << std::endl; + + // Verify resource value is set correctly + std::this_thread::sleep_for(std::chrono::seconds(1)); + value = AuxRoutines::readFromFile(testResourceName); + newValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Configured Value: " << newValue << std::endl; + E_ASSERT((newValue == 231)); + + // Wait for request to expire and verify resource resets + std::this_thread::sleep_for(std::chrono::seconds(10)); + value = AuxRoutines::readFromFile(testResourceName); + newValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Reset Value: " << newValue << std::endl; + E_ASSERT((newValue == originalValue)); + + // Cleanup + kill(pid, SIGTERM); + waitpid(pid, nullptr, 0); + } +}) + +/** + * API under test: Tune / Untune + * - Spawns vi text editor and verifies that the per-app config + * correctly tunes the resource node when vi is running + * - Verifies resource value is set when vi is active + * - Verifies resource value resets once the request expires + */ +URM_TEST(TestViPerAppConfigValidated, { + const std::string modelPath = FT_MODEL_PATH; + + // Wait for service to settle + std::this_thread::sleep_for(std::chrono::milliseconds(1800)); + + // Verify model file exists + int64_t modelExists = AuxRoutines::fileExists(modelPath); + if (modelExists <= 0) { + std::cout << LOG_BASE << "Model not found, skipping test" << std::endl; + E_ASSERT(false); + return; + } + + std::string testResourceName = "/etc/urm/tests/nodes/target_test_resource1.txt"; + int32_t testResourceOriginalValue = 240; + + std::string value; + int32_t originalValue, newValue; + + // Verify original resource value + value = AuxRoutines::readFromFile(testResourceName); + originalValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Original Value: " << originalValue << std::endl; + E_ASSERT((originalValue == testResourceOriginalValue)); + + // Verify executable exists before attempting spawn + if (!AuxRoutines::fileExists("/usr/bin/vi")) { + std::cout << LOG_BASE << "Executable not found: " <<"/usr/bin/vi"<< std::endl; + std::cout << LOG_BASE << "Status: SKIPPED" << std::endl; + + // return from this Test case if not .exe not exist + return; + } + + // Spawn vi + pid_t pid = fork(); + if (pid == 0) { + // Child process: redirect output to suppress application noise + int devNull = open("/dev/null", O_WRONLY); + if (devNull != -1) { + dup2(devNull, STDERR_FILENO); + dup2(devNull, STDOUT_FILENO); + close(devNull); + } + execl("/usr/bin/vi", "vi", "-e", "-s", nullptr); + exit(EXIT_FAILURE); + + } else if (pid > 0) { + // Wait for vi to start + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + // Verify process is alive + char commPath[256]; + snprintf(commPath, sizeof(commPath), "/proc/%d/comm", pid); + E_ASSERT(AuxRoutines::fileExists(commPath)); + + // Read and log process name + std::string comm = AuxRoutines::readFromFile(commPath); + if (!comm.empty() && comm.back() == '\n') { + comm.pop_back(); + } + std::cout << LOG_BASE << "Process: " << comm << " (PID: " << pid << ")" << std::endl; + + // Verify resource value is set correctly + std::this_thread::sleep_for(std::chrono::seconds(1)); + value = AuxRoutines::readFromFile(testResourceName); + newValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Configured Value: " << newValue << std::endl; + E_ASSERT((newValue == 231)); + + // Wait for request to expire and verify resource resets + std::this_thread::sleep_for(std::chrono::seconds(10)); + value = AuxRoutines::readFromFile(testResourceName); + newValue = C_STOI(value); + std::cout << LOG_BASE << testResourceName << " Reset Value: " << newValue << std::endl; + E_ASSERT((newValue == originalValue)); + + // Cleanup + kill(pid, SIGTERM); + waitpid(pid, nullptr, 0); + } +}) From 955fda289010b68bf219a67d0e9be2956d9568d0 Mon Sep 17 00:00:00 2001 From: varusing Date: Tue, 3 Mar 2026 10:48:23 +0530 Subject: [PATCH 6/8] Add ML-based contextual classification tests with YAML config and integration tests Signed-off-by: varusing --- tests/CMakeLists.txt | 8 +++++++- tests/Integration/CCIntegrationTest.cpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c96e283cb..ea645dc6e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,7 +67,7 @@ if(BUILD_TESTS) install(TARGETS UrmComponentTests DESTINATION ${CMAKE_INSTALL_BINDIR}) - add_executable(UrmIntegrationTests Integration/IntegrationTests.cpp Integration/CCIntegrationTest.cpp) + add_executable(UrmIntegrationTests Integration/IntegrationTests.cpp) add_definitions(-DUSE_FLORET=1) target_link_libraries(UrmIntegrationTests PUBLIC UrmAuxUtils @@ -75,6 +75,12 @@ if(BUILD_TESTS) RestuneTestUtils ${LIBYAML_LIBRARIES}) target_include_directories(UrmIntegrationTests PRIVATE ${LIBYAML_INCLUDE_DIRS}) + + if(BUILD_CLASSIFIER) + target_sources(UrmIntegrationTests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Integration/CCIntegrationTest.cpp) + endif() + install(TARGETS UrmIntegrationTests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/tests/Integration/CCIntegrationTest.cpp b/tests/Integration/CCIntegrationTest.cpp index b939f8603..72103d95d 100644 --- a/tests/Integration/CCIntegrationTest.cpp +++ b/tests/Integration/CCIntegrationTest.cpp @@ -7,6 +7,7 @@ #include "TestUtils.h" #include "URMTests.h" #include "UrmAPIs.h" +#include // Test configuration and paths #define TEST_CLASS "INTEGRATION" From bd285f2ff5c6d52989b65dae61dca3d28134770b Mon Sep 17 00:00:00 2001 From: varusing Date: Tue, 3 Mar 2026 13:53:17 +0530 Subject: [PATCH 7/8] Add ML-based contextual classification tests with YAML config and integration tests Signed-off-by: varusing --- tests/CMakeLists.txt | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ea645dc6e..05474889c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,19 +56,40 @@ if(BUILD_TESTS) ${CMAKE_SOURCE_DIR}/common/Include) if(BUILD_CLASSIFIER) - target_sources(UrmComponentTests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/Component/ContextClassificationTest.cpp) + set(FLORET_FOUND FALSE) + set(FLORET_LIBRARIES "") + set(FLORET_INCLUDE_DIRS "") - target_link_libraries(UrmComponentTests PUBLIC ContextualClassifier - ml_inference_lib) + if(ENABLE_FLORET) + # Use pkg-config to find floret + pkg_check_modules(PC_FLORET QUIET floret) + if(PC_FLORET_FOUND) + set(FLORET_FOUND TRUE) + set(FLORET_LIBRARIES ${PC_FLORET_LIBRARIES}) + set(FLORET_INCLUDE_DIRS ${PC_FLORET_INCLUDE_DIRS}) + endif() + endif() - target_include_directories(UrmComponentTests PRIVATE ${CMAKE_SOURCE_DIR}/contextual-classifier/Include) + if(FLORET_FOUND) + message(STATUS "floret found, building MLInference with USE_FLORET=1") + add_definitions(-DUSE_FLORET=1) + + target_sources(UrmComponentTests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Component/ContextClassificationTest.cpp) + + target_link_libraries(UrmComponentTests PUBLIC ContextualClassifier + ml_inference_lib) + + target_include_directories(UrmComponentTests PRIVATE + ${CMAKE_SOURCE_DIR}/contextual-classifier/Include) + else() + message(STATUS "floret not found") + endif() endif() install(TARGETS UrmComponentTests DESTINATION ${CMAKE_INSTALL_BINDIR}) add_executable(UrmIntegrationTests Integration/IntegrationTests.cpp) - add_definitions(-DUSE_FLORET=1) target_link_libraries(UrmIntegrationTests PUBLIC UrmAuxUtils UrmClient From db4cd94677b499d91b60e4135a45f57d2726111a Mon Sep 17 00:00:00 2001 From: varusing Date: Tue, 3 Mar 2026 14:25:32 +0530 Subject: [PATCH 8/8] Add ML-based contextual classification tests with YAML config and integration tests Signed-off-by: varusing --- tests/Component/ContextClassificationTest.cpp | 1 + tests/Integration/CCIntegrationTest.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/Component/ContextClassificationTest.cpp b/tests/Component/ContextClassificationTest.cpp index 7855bc466..522630530 100644 --- a/tests/Component/ContextClassificationTest.cpp +++ b/tests/Component/ContextClassificationTest.cpp @@ -31,6 +31,7 @@ // Test configuration paths and identifiers #define TEST_CLASS "COMPONENT" +#define TEST_SUBCAT "CONTEXT_CLASSIFIER" #define CLASSIFIER_CONFIGS_DIR "/etc/urm/classifier/" #define TEST_CONFIG_PATH "/etc/urm/tests/configs/ClassificationAppPredConfig.yaml" diff --git a/tests/Integration/CCIntegrationTest.cpp b/tests/Integration/CCIntegrationTest.cpp index 72103d95d..1e2f3c285 100644 --- a/tests/Integration/CCIntegrationTest.cpp +++ b/tests/Integration/CCIntegrationTest.cpp @@ -11,6 +11,7 @@ // Test configuration and paths #define TEST_CLASS "INTEGRATION" +#define TEST_SUBCAT "CC_INTEGRATION" #define CLASSIFIER_CONFIGS_DIR "/etc/urm/classifier/" // Path to the Floret supervised learning model binary