diff --git a/contextual-classifier/ContextualClassifier.cpp b/contextual-classifier/ContextualClassifier.cpp index 5082631b0..d29b03534 100644 --- a/contextual-classifier/ContextualClassifier.cpp +++ b/contextual-classifier/ContextualClassifier.cpp @@ -1,28 +1,28 @@ // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. // SPDX-License-Identifier: BSD-3-Clause-Clear -#include #include +#include #include #include #include -#include #include -#include #include -#include +#include +#include +#include #include "Utils.h" -#include "UrmPlatformAL.h" -#include "Request.h" #include "Logger.h" +#include "Request.h" +#include "Inference.h" +#include "Extensions.h" #include "AuxRoutines.h" +#include "UrmPlatformAL.h" +#include "SignalRegistry.h" +#include "RestuneInternal.h" #include "ContextualClassifier.h" #include "ClientGarbageCollector.h" -#include "RestuneInternal.h" -#include "SignalRegistry.h" -#include "Extensions.h" -#include "Inference.h" #define CLASSIFIER_TAG "CONTEXTUAL_CLASSIFIER" #define CLASSIFIER_CONFIGS_DIR "/etc/urm/classifier/" @@ -55,7 +55,7 @@ static ContextualClassifier *gClassifier = nullptr; static const int32_t pendingQueueControlSize = 30; ContextualClassifier::ContextualClassifier() { - mInference = GetInferenceObject(); + this->mInference = GetInferenceObject(); } void ContextualClassifier::untuneRequestHelper(int64_t handle) { @@ -81,7 +81,7 @@ void ContextualClassifier::untuneRequestHelper(int64_t handle) { } catch(const std::exception& e) { LOGE(CLASSIFIER_TAG, - "Failed to move per-app threads to cgroup, Error: " + std::string(e.what())); + "Failed to create untune request, Error: " + std::string(e.what())); } } @@ -153,24 +153,24 @@ ContextualClassifier::~ContextualClassifier() { } ErrCode ContextualClassifier::Init() { - LOGI(CLASSIFIER_TAG, "Classifier module init."); + LOGI(CLASSIFIER_TAG, "Classifier module init"); this->LoadIgnoredProcesses(); // Single worker thread for classification this->mClassifierMain = std::thread(&ContextualClassifier::ClassifierMain, this); - if (this->mNetLinkComm.connect() == -1) { - LOGE(CLASSIFIER_TAG, "Failed to connect to netlink socket."); + if(this->mNetLinkComm.connect() == -1) { + LOGE(CLASSIFIER_TAG, "Failed to connect to netlink socket"); return RC_SOCKET_OP_FAILURE; } - if (this->mNetLinkComm.setListen(true) == -1) { - LOGE(CLASSIFIER_TAG, "Failed to set proc event listener."); + if(this->mNetLinkComm.setListen(true) == -1) { + LOGE(CLASSIFIER_TAG, "Failed to set proc event listener"); mNetLinkComm.closeSocket(); return RC_SOCKET_OP_FAILURE; } - LOGI(CLASSIFIER_TAG, "Now listening for process events."); + LOGI(CLASSIFIER_TAG, "Now listening for process events"); this->mNetlinkThread = std::thread(&ContextualClassifier::HandleProcEv, this); return RC_SUCCESS; @@ -179,27 +179,25 @@ ErrCode ContextualClassifier::Init() { ErrCode ContextualClassifier::Terminate() { LOGI(CLASSIFIER_TAG, "Classifier module terminate."); - if (this->mNetLinkComm.getSocket() != -1) { + if(this->mNetLinkComm.getSocket() != -1) { this->mNetLinkComm.setListen(false); } - { - std::unique_lock lock(this->mQueueMutex); - this->mNeedExit = true; - // Clear any pending PIDs so the worker doesn't see stale entries - while (!this->mPendingEv.empty()) { - this->mPendingEv.pop(); - } + std::unique_lock lock(this->mQueueMutex); + this->mNeedExit = true; + // Clear any pending PIDs so the worker doesn't see stale entries + while(!this->mPendingEv.empty()) { + this->mPendingEv.pop(); } this->mQueueCond.notify_all(); this->mNetLinkComm.closeSocket(); - if (this->mNetlinkThread.joinable()) { + if(this->mNetlinkThread.joinable()) { this->mNetlinkThread.join(); } - if (this->mClassifierMain.joinable()) { + if(this->mClassifierMain.joinable()) { this->mClassifierMain.join(); } @@ -267,7 +265,7 @@ void ContextualClassifier::ClassifierMain() { // Step 4: // Configure any per-app config specified signals. - this->configureAssociatedAppSignals(ev.pid, ev.tgid, comm); + this->configureAppSignals(ev.pid, ev.tgid, comm); // Step 5: If the post processing block exists, call it // It might provide us a more specific sigID or sigType @@ -296,7 +294,7 @@ void ContextualClassifier::ClassifierMain() { } } -int ContextualClassifier::HandleProcEv() { +int32_t ContextualClassifier::HandleProcEv() { pthread_setname_np(pthread_self(), "urmNetlinkListener"); int32_t rc = 0; @@ -363,12 +361,13 @@ int32_t ContextualClassifier::ClassifyProcess(pid_t processPid, uint32_t &ctxDetails) { (void)processTgid; (void)ctxDetails; + CC_TYPE context = CC_APP; // Check if the process is still alive if(!AuxRoutines::fileExists(COMM(processPid))) { LOGD(CLASSIFIER_TAG, - "Skipping inference, process is dead: "+ comm); + "Skipping inference, process is dead: " + comm); return CC_IGNORE; } @@ -403,6 +402,7 @@ void ContextualClassifier::ApplyActions(uint32_t sigId, void ContextualClassifier::RemoveActions(pid_t processPid, pid_t processTgid) { (void)processPid; (void)processTgid; + return; } @@ -459,7 +459,7 @@ void ContextualClassifier::LoadIgnoredProcesses() { } } } - LOGI(CLASSIFIER_TAG, "Loaded filter processes."); + LOGI(CLASSIFIER_TAG, "Loaded filter processes"); } int8_t ContextualClassifier::shouldProcBeIgnored(int32_t evType, pid_t pid) { @@ -543,10 +543,9 @@ void ContextualClassifier::MoveAppThreadsToCGroup(pid_t incomingPID, } } -void ContextualClassifier::configureAssociatedAppSignals( - pid_t incomingPID, - pid_t incomingTID, - const std::string& comm) { +void ContextualClassifier::configureAppSignals(pid_t incomingPID, + pid_t incomingTID, + const std::string& comm) { try { // Configure any associated signal AppConfig* appConfig = AppConfigs::getInstance()->getAppConfig(comm); @@ -574,20 +573,20 @@ void ContextualClassifier::configureAssociatedAppSignals( } } catch(const std::exception& e) { LOGE(CLASSIFIER_TAG, - "Failed to move per-app threads to cgroup, Error: " + std::string(e.what())); + "Failed to acquire per-app config signal, Error: " + std::string(e.what())); } } // Public C interface exported from the contextual-classifier shared library. // These are what the URM module entrypoints will call. -extern "C" ErrCode cc_init(void) { +extern "C" ErrCode ccInit(void) { if(gClassifier == nullptr) { gClassifier = new ContextualClassifier(); } return gClassifier->Init(); } -extern "C" ErrCode cc_terminate(void) { +extern "C" ErrCode ccTerminate(void) { if(gClassifier != nullptr) { ErrCode opStatus = gClassifier->Terminate(); delete gClassifier; diff --git a/contextual-classifier/ContextualClassifierInit.cpp b/contextual-classifier/ContextualClassifierInit.cpp index 984435aee..fecceef29 100644 --- a/contextual-classifier/ContextualClassifierInit.cpp +++ b/contextual-classifier/ContextualClassifierInit.cpp @@ -47,11 +47,11 @@ static ErrCode init(void *arg = nullptr) { dlerror(); - g_cc_init = reinterpret_cast(dlsym(g_cc_handle, "cc_init")); + g_cc_init = reinterpret_cast(dlsym(g_cc_handle, "ccInit")); const char *err = dlerror(); if (err != nullptr || !g_cc_init) { LOGE(CLASSIFIER_TAG, - format_string("Failed to resolve cc_init in %s: %s", so_name, + format_string("Failed to resolve ccInit in %s: %s", so_name, err ? err : "unknown")); dlclose(g_cc_handle); g_cc_handle = nullptr; @@ -60,11 +60,11 @@ static ErrCode init(void *arg = nullptr) { return RC_SUCCESS; } - g_cc_term = reinterpret_cast(dlsym(g_cc_handle, "cc_terminate")); + g_cc_term = reinterpret_cast(dlsym(g_cc_handle, "ccTerminate")); err = dlerror(); if (err != nullptr || !g_cc_term) { LOGE(CLASSIFIER_TAG, - format_string("Failed to resolve cc_terminate in %s: %s", so_name, + format_string("Failed to resolve ccTerminate in %s: %s", so_name, err ? err : "unknown")); dlclose(g_cc_handle); g_cc_handle = nullptr; diff --git a/contextual-classifier/Include/ContextualClassifier.h b/contextual-classifier/Include/ContextualClassifier.h index bf7fd16d0..7604e1536 100644 --- a/contextual-classifier/Include/ContextualClassifier.h +++ b/contextual-classifier/Include/ContextualClassifier.h @@ -108,7 +108,7 @@ class ContextualClassifier { const std::string& comm, int32_t cgroupIdentifier); - void configureAssociatedAppSignals(pid_t incomingPID, + void configureAppSignals(pid_t incomingPID, pid_t incomingTID, const std::string& comm); diff --git a/contextual-classifier/NetLinkComm.cpp b/contextual-classifier/NetLinkComm.cpp index 82ce5db0a..e0832b4cb 100644 --- a/contextual-classifier/NetLinkComm.cpp +++ b/contextual-classifier/NetLinkComm.cpp @@ -11,29 +11,39 @@ #include "NetLinkComm.h" #include "ContextualClassifier.h" -#define CLASSIFIER_TAG "NetLinkComm" +#define CLASSIFIER_TAG "CLASSIFIER_NETLINK" #define PROCP_THRESH 50 -static pid_t getParent(pid_t pid) { - std::string statusFile = "/proc/" + std::to_string(pid) + "/status"; - std::ifstream file(statusFile); - std::string line; +static int8_t procHasControlTerminal(pid_t pid) { + std::string procStatPath = STAT(pid); + std::string stat = AuxRoutines::readFromFile(procStatPath); + if(stat.empty()) { + return false; + } - if(!file.is_open()) { - return 0; + // Find the closing ')' of the comm field. + size_t pos = stat.rfind(')'); + if(pos == std::string::npos) { + return false; } - while(std::getline(file, line)) { - if(line.rfind("PPid:", 0) == 0) { - std::istringstream iss(line); - std::string label; - pid_t parentPid; - iss >> label >> parentPid; - return parentPid; - } + // Start parsing right after ')' and skip any spaces. + pos++; + while(pos < stat.size() && stat[pos] == ' ') { + pos++; + } + if(pos >= stat.size()) { + return false; } - return 0; + std::istringstream statStream(stat.substr(pos)); + + char state = '\0'; + int64_t ppid = 0, pgrp = 0, session = 0, ttyNr = 0; + statStream >> state >> ppid >> pgrp >> session >> ttyNr; + + // For daemon / system services, tty number (controlling terminal) will be 0. + return (ttyNr != 0); } NetLinkComm::NetLinkComm() { @@ -148,7 +158,7 @@ int32_t NetLinkComm::recvEvent(ProcEvent &ev) { ev.type = CC_APP_OPEN; rc = CC_APP_OPEN; - if(!AuxRoutines::fileExists(COMM(ev.pid)) || (getParent(ev.pid) <= PROCP_THRESH)) { + if(!AuxRoutines::fileExists(COMM(ev.pid)) || !procHasControlTerminal(ev.pid)) { rc = ev.type = CC_IGNORE; } break; @@ -159,7 +169,7 @@ int32_t NetLinkComm::recvEvent(ProcEvent &ev) { ev.type = CC_APP_CLOSE; rc = CC_APP_CLOSE; - if(!AuxRoutines::fileExists(COMM(ev.pid)) || (getParent(ev.pid) <= PROCP_THRESH)) { + if(!AuxRoutines::fileExists(COMM(ev.pid)) || !procHasControlTerminal(ev.pid)) { rc = ev.type = CC_IGNORE; } break; diff --git a/modula/Common/Include/Utils.h b/modula/Common/Include/Utils.h index 8392b4ad9..241fff7ce 100644 --- a/modula/Common/Include/Utils.h +++ b/modula/Common/Include/Utils.h @@ -126,6 +126,7 @@ typedef void (*MessageReceivedCallback)(int32_t, MsgForwardInfo*); #define COMM_S(pidstr) ("/proc/" + pidstr + "/comm") #define STATUS(pid) ("/proc/" + std::to_string(pid) + "/status") #define CMDLINE(pid) ("/proc/" + std::to_string(pid) + "/cmdline") +#define STAT(pid) ("/proc/" + std::to_string(pid) + "/stat") #define CONCAT_IMPL(a, b) a##b #define CONCAT(a, b) CONCAT_IMPL(a, b) diff --git a/tests/framework/mini.h b/tests/framework/mini.h deleted file mode 100644 index 2296119b5..000000000 --- a/tests/framework/mini.h +++ /dev/null @@ -1,1065 +0,0 @@ -// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -// SPDX-License-Identifier: BSD-3-Clause-Clear - -#ifndef MINI_HPP_INCLUDED -#define MINI_HPP_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // for std::setprecision -#include // for std::max -#include // for reports -#include // for grouping reports - -namespace mtest { -struct RunResult; -// ---------- Config ---------- -#ifndef MTEST_ENABLE_EXCEPTIONS -#define MTEST_ENABLE_EXCEPTIONS 1 -#endif - -#ifndef MTEST_DEFAULT_OUTPUT_TAP -#define MTEST_DEFAULT_OUTPUT_TAP 0 -#endif - -#ifndef MTEST_DEFAULT_THREADS -#define MTEST_DEFAULT_THREADS 1 -#endif - -// ---------- Core types ---------- -struct TestContext { - const char* suite = nullptr; - std::string name; // dynamic instance names supported - const char* tag = nullptr; // "unit", "module", "usecase", … - const char* file = nullptr; - int line = 0; - std::string message; - bool failed = false; - - // Runtime markers - bool mark_skip = false; - bool mark_xfail = false; - std::string mark_reason; -}; - -using testfn = std::function; - -struct TestCase { - const char* suite; - std::string name; // allow dynamic instance names - const char* tag; - const char* file; - int line; - testfn fn; - - // Static markers - bool skip = false; - bool xfail = false; - const char* reason = nullptr; - bool xfail_strict = false; // per-test strict override -}; - -enum class CaseStatus { PASS, FAIL, SKIP, XFAIL, XPASS }; - -// ---------- Registry ---------- -inline std::vector& registry() { - static std::vector r; - return r; -} - -struct Registrar { - // C-string name - Registrar(const char* suite, const char* name, const char* tag, - const char* file, int line, testfn fn, - bool skip=false, bool xfail=false, - const char* reason=nullptr, bool xfail_strict=false) { - registry().push_back({suite, std::string(name), tag, file, line, std::move(fn), - skip, xfail, reason, xfail_strict}); - } - // std::string name (parameterized cases) - Registrar(const char* suite, const std::string& name, const char* tag, - const char* file, int line, testfn fn, - bool skip=false, bool xfail=false, - const char* reason=nullptr, bool xfail_strict=false) { - registry().push_back({suite, name, tag, file, line, std::move(fn), - skip, xfail, reason, xfail_strict}); - } -}; - -// ---------- Utilities ---------- -namespace detail { - // C++17-friendly to_string_any - template - inline std::string to_string_any(const T& v) { - std::ostringstream oss; - oss << v; - return oss.str(); - } - - // ANSI colors - struct Colors { - const char* reset = "\x1b[0m"; - const char* bold = "\x1b[1m"; - const char* red = "\x1b[31m"; - const char* green = "\x1b[32m"; - const char* yellow = "\x1b[33m"; - const char* cyan = "\x1b[36m"; - }; - - inline bool env_no_color() { - const char* nc = std::getenv("NO_COLOR"); - return nc && *nc; - } - - inline std::string fmt_pass(const TestCase& tc, double ms, bool tap, bool color) { - Colors c; - std::ostringstream oss; - if (tap) { - oss << "ok - " << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms << " ms)\n"; - } else { - if (color) oss << c.green; - oss << "[PASS] "; - if (color) oss << c.reset; - oss << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms << " ms)\n"; - } - return oss.str(); - } - - inline std::string fmt_fail(const TestCase& tc, double ms, const TestContext& ctx, - bool tap, bool color) { - Colors c; - std::ostringstream oss; - if (tap) { - oss << "not ok - " << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms << " ms)\n"; - oss << " ---\n file: " << tc.file << "\n line: " << tc.line - << "\n msg: " << ctx.message << "\n ...\n"; - } else { - if (color) oss << c.red; - oss << "[FAIL] "; - if (color) oss << c.reset; - oss << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms << " ms)\n " - << tc.file << ":" << tc.line << "\n " << ctx.message << "\n"; - } - return oss.str(); - } - - inline std::string fmt_skip(const TestCase& tc, double ms, const char* reason, - bool tap, bool color, int tap_index) { - Colors c; - std::ostringstream oss; - if (tap) { - oss << "ok " << tap_index << " - " << tc.suite << "." << tc.name - << " [" << tc.tag << "] # SKIP " << (reason ? reason : "") << "\n"; - } else { - if (color) oss << c.yellow; - oss << "[SKIP] "; - if (color) oss << c.reset; - oss << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms - << " ms) " << (reason ? reason : "") << "\n"; - } - return oss.str(); - } - - inline std::string fmt_xfail(const TestCase& tc, double ms, const char* reason, - bool tap, bool color, int tap_index) { - Colors c; - std::ostringstream oss; - if (tap) { - oss << "not ok " << tap_index << " - " << tc.suite << "." << tc.name - << " [" << tc.tag << "] (" << std::fixed << std::setprecision(3) << ms - << " ms) # TODO " << (reason ? reason : "expected failure") << "\n"; - } else { - if (color) oss << c.yellow; - oss << "[XFAIL] "; - if (color) oss << c.reset; - oss << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms - << " ms) " << (reason ? reason : "") << "\n"; - } - return oss.str(); - } - - inline std::string fmt_xpass(const TestCase& tc, double ms, const char* reason, - bool tap, bool color, bool strict, int tap_index) { - Colors c; - std::ostringstream oss; - if (tap) { - oss << "ok " << tap_index << " - " << tc.suite << "." << tc.name - << " [" << tc.tag << "] (" << std::fixed << std::setprecision(3) << ms - << " ms) # TODO XPASS: " << (reason ? reason : "unexpected pass") << "\n"; - } else { - if (strict) { - if (color) oss << c.red; - oss << "[XPASS(strict)] "; - if (color) oss << c.reset; - } else { - if (color) oss << c.cyan; - oss << "[XPASS] "; - if (color) oss << c.reset; - } - oss << tc.suite << "." << tc.name << " [" << tc.tag << "] (" - << std::fixed << std::setprecision(3) << ms - << " ms) " << (reason ? reason : "") << "\n"; - } - return oss.str(); - } - - // -------- Reporting helpers (prototypes only; impls appear later) -------- - // Forward declare writers so run_all can call them; implementations are below - - - void write_json_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary); - - void write_junit_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary, - bool xfail_as_skip); - - void write_markdown_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary); - - -} // namespace detail - -// ---------- Assertions ---------- -#define MTEST_STRINGIFY(x) #x -#define MTEST_STR(x) MTEST_STRINGIFY(x) - -#define MT_REQUIRE(ctx, expr) do { \ - if (!(expr)) { \ - (ctx).failed = true; \ - (ctx).message = std::string("REQUIRE failed: ") + #expr; \ - if (MTEST_ENABLE_EXCEPTIONS) throw std::runtime_error((ctx).message); \ - return; \ - } \ -} while(0) - -#define MT_CHECK(ctx, expr) do { \ - if (!(expr)) { \ - (ctx).failed = true; \ - (ctx).message = std::string("CHECK failed: ") + #expr; \ - } \ -} while(0) - -#define MT_REQUIRE_EQ(ctx, a, b) do { \ - auto va = (a); auto vb = (b); \ - if (!(va == vb)) { \ - (ctx).failed = true; \ - std::ostringstream _m; \ - _m << "REQUIRE_EQ failed: " << #a << " == " << #b \ - << " (got " << va << " vs " << vb << ")"; \ - (ctx).message = _m.str(); \ - if (MTEST_ENABLE_EXCEPTIONS) throw std::runtime_error((ctx).message); \ - return; \ - } \ -} while(0) - -#define MT_CHECK_EQ(ctx, a, b) do { \ - auto va = (a); auto vb = (b); \ - if (!(va == vb)) { \ - (ctx).failed = true; \ - std::ostringstream _m; \ - _m << "CHECK_EQ failed: " << #a << " == " << #b \ - << " (got " << va << " vs " << vb << ")"; \ - (ctx).message = _m.str(); \ - } \ -} while(0) - -#define MT_FAIL(ctx, msg) do { \ - (ctx).failed = true; \ - (ctx).message = std::string("FAIL: ") + (msg); \ - if (MTEST_ENABLE_EXCEPTIONS) throw std::runtime_error((ctx).message); \ - return; \ -} while(0) - - -// ---------- Markers (runtime) ---------- -#define MT_SKIP(ctx, reason) do { \ - (ctx).mark_skip = true; \ - (ctx).mark_reason = (reason); \ - return; \ -} while(0) - -#define MT_MARK_XFAIL(ctx, reason) do { \ - (ctx).mark_xfail = true; \ - (ctx).mark_reason = (reason); \ -} while(0) - - -#ifndef MTEST_OVERRIDE_ASSERT -#define MTEST_OVERRIDE_ASSERT 1 -#endif -#if MTEST_OVERRIDE_ASSERT -namespace detail { - inline void assert_true(bool cond, const char* expr, const char* file, int line) { - if (!cond) { -#if MTEST_ENABLE_EXCEPTIONS - throw std::runtime_error(std::string("assert(") + expr + ") failed at " - + file + ":" + std::to_string(line)); -#else - std::fprintf(stderr, "assert(%s) failed at %s:%d\n", expr, file, line); -#endif - } - } -} -#ifdef assert -#undef assert -#endif -#define assert(expr) ::mtest::detail::assert_true((expr), #expr, __FILE__, __LINE__) -#endif - - -// ---------- Fixtures ---------- -struct Fixture { - virtual ~Fixture() = default; - virtual void setup(TestContext&) {} - virtual void teardown(TestContext&) {} -}; - -template -inline void with_fixture(TestContext& ctx, - const std::function& body) { - F f; - f.setup(ctx); - body(f, ctx); - f.teardown(ctx); -} - -// ---------- Registration macros ---------- -#define MT_TEST(suite, name, tag) \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl \ - ); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) - -#define MT_TEST_F(suite, name, tag, FixtureType) \ - static void suite##_##name##_body(FixtureType&, mtest::TestContext&); \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl \ - ); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) { \ - mtest::with_fixture(ctx, std::bind( \ - suite##_##name##_body, std::placeholders::_1, std::placeholders::_2)); \ - } \ - static void suite##_##name##_body(FixtureType& f, mtest::TestContext& ctx) - -#define MT_TEST_F_END - -// Parameterized tests -#define MT_TEST_P(suite, name, tag, ParamType, params) \ - static void suite##_##name##_impl(mtest::TestContext&, const ParamType& param); \ - namespace suite##_##name##_param_ns { \ - static struct registrar_t { \ - registrar_t() { \ - for (const ParamType& _p : params) { \ - std::string _n = std::string(MTEST_STRINGIFY(name)) + "(" + mtest::detail::to_string_any(_p) + ")"; \ - mtest::Registrar( \ - MTEST_STRINGIFY(suite), _n, tag, __FILE__, __LINE__, \ - std::bind(suite##_##name##_impl, std::placeholders::_1, _p) \ - ); \ - } \ - } \ - } _registrar_instance; \ - } \ - static void suite##_##name##_impl(mtest::TestContext& ctx, const ParamType& param) - - -#define MT_TEST_FP(suite, name, tag, FixtureType, ParamType, params) \ - static void suite##_##name##_body(FixtureType&, mtest::TestContext&, const ParamType&); \ - static void suite##_##name##_impl(mtest::TestContext&); \ - namespace suite##_##name##_fp_ns { \ - static struct registrar_t { \ - registrar_t() { \ - for (const ParamType& _p : params) { \ - std::string _n = std::string(MTEST_STRINGIFY(name)) + "(" + mtest::detail::to_string_any(_p) + ")"; \ - mtest::Registrar( \ - MTEST_STRINGIFY(suite), _n, tag, __FILE__, __LINE__, \ - std::bind(suite##_##name##_impl, std::placeholders::_1) \ - ); \ - } \ - } \ - } _registrar_instance; \ - } \ - static void suite##_##name##_impl(mtest::TestContext& ctx) { \ - (void)ctx; \ - } \ - static void suite##_##name##_body(FixtureType& f, mtest::TestContext& ctx, const ParamType& param) - - -// ---------- Static marker macros ---------- -#define MT_TEST_XFAIL(suite, name, tag, reason) \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl, \ - /*skip*/false, /*xfail*/true, reason, /*xfail_strict*/false); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) - -#define MT_TEST_SKIP(suite, name, tag, reason) \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl, \ - /*skip*/true, /*xfail*/false, reason, /*xfail_strict*/false); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) - -#define MT_TEST_F_XFAIL(suite, name, tag, FixtureType, reason) \ - static void suite##_##name##_body(FixtureType&, mtest::TestContext&); \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl, \ - /*skip*/false, /*xfail*/true, reason, /*xfail_strict*/false); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) { \ - mtest::with_fixture(ctx, std::bind( \ - suite##_##name##_body, std::placeholders::_1, std::placeholders::_2)); \ - } \ - static void suite##_##name##_body(FixtureType& f, mtest::TestContext& ctx) - -#define MT_TEST_F_SKIP(suite, name, tag, FixtureType, reason) \ - static void suite##_##name##_body(FixtureType&, mtest::TestContext&); \ - static void suite##_##name##_impl(mtest::TestContext&); \ - static mtest::Registrar suite##_##name##_reg( \ - MTEST_STRINGIFY(suite), MTEST_STRINGIFY(name), tag, \ - __FILE__, __LINE__, suite##_##name##_impl, \ - /*skip*/true, /*xfail*/false, reason, /*xfail_strict*/false); \ - static void suite##_##name##_impl(mtest::TestContext& ctx) { \ - mtest::with_fixture(ctx, std::bind( \ - suite##_##name##_body, std::placeholders::_1, std::placeholders::_2)); \ - } \ - static void suite##_##name##_body(FixtureType& f, mtest::TestContext& ctx) - - -// ---------- Runner ---------- -struct RunOptions { - const char* name_contains = nullptr; // substring filter on test name - const char* tag_equals = nullptr; // exact tag filter - bool tap_output = MTEST_DEFAULT_OUTPUT_TAP != 0; - bool stop_on_fail = false; - bool summary_only = false; - bool color_output = true; // colored output - int threads = MTEST_DEFAULT_THREADS; // parallelism - - // XFAIL strict mode: XPASS counts as failure when true - bool xfail_strict = false; - - // Reports - const char* report_json = nullptr; // --report-json= - const char* report_junit = nullptr; // --report-junit= - const char* report_md = nullptr; // --report-md= - bool junit_xfail_as_skip = true; // treat XFAIL as skipped in JUnit -}; - -struct RunResult { - int total = 0; - int failed = 0; - int passed = 0; - double ms_total = 0.0; - - // New counters - int skipped = 0; - int xfailed = 0; - int xpassed = 0; -}; - -inline bool match_filter(const TestCase& tc, const RunOptions& opt) { - if (opt.name_contains) { - if (tc.name.find(opt.name_contains) == std::string::npos) - return false; - } - if (opt.tag_equals) { - if (std::strcmp(tc.tag, opt.tag_equals) != 0) - return false; - } - return true; -} - -struct CaseResult { - CaseStatus status = CaseStatus::PASS; - double ms = 0.0; - std::string out; - std::string msg; - bool strict_xpass = false; - std::string reason; -}; - -inline CaseResult run_one(const TestCase& tc, const RunOptions& opt, int tap_index) { - CaseResult cr; - TestContext ctx; - ctx.suite = tc.suite; ctx.name = tc.name; ctx.tag = tc.tag; - ctx.file = tc.file; ctx.line = tc.line; - - auto t0 = std::chrono::steady_clock::now(); - - // Static skip: short-circuit - if (tc.skip) { - auto t1s = std::chrono::steady_clock::now(); - cr.ms = std::chrono::duration(t1s - t0).count(); - cr.status = CaseStatus::SKIP; - cr.reason = (tc.reason ? tc.reason : ""); - cr.out = detail::fmt_skip(tc, cr.ms, tc.reason, opt.tap_output, - opt.color_output && !detail::env_no_color(), - tap_index); - return cr; - } - -#if MTEST_ENABLE_EXCEPTIONS - try { - tc.fn(ctx); - } catch (const std::exception& e) { - ctx.failed = true; - ctx.message = std::string("uncaught: ") + e.what(); - } catch (...) { - ctx.failed = true; - ctx.message = "uncaught: unknown exception"; - } -#else - tc.fn(ctx); -#endif - auto t1 = std::chrono::steady_clock::now(); - double ms = std::chrono::duration(t1 - t0).count(); - cr.ms = ms; - - // Runtime skip? - if (ctx.mark_skip) { - cr.status = CaseStatus::SKIP; - cr.reason = ctx.mark_reason; - cr.out = detail::fmt_skip(tc, ms, ctx.mark_reason.c_str(), - opt.tap_output, - opt.color_output && !detail::env_no_color(), - tap_index); - return cr; - } - - // XFAIL logic (static or runtime) - bool is_xfail = tc.xfail || ctx.mark_xfail; - const char* why = tc.reason ? tc.reason : (ctx.mark_reason.empty() ? nullptr : ctx.mark_reason.c_str()); - bool strict = tc.xfail_strict || opt.xfail_strict; - - if (is_xfail) { - cr.reason = (why ? why : ""); - if (ctx.failed) { - cr.status = CaseStatus::XFAIL; - cr.msg = ctx.message; - cr.out = detail::fmt_xfail(tc, ms, why, opt.tap_output, - opt.color_output && !detail::env_no_color(), - tap_index); - } else { - cr.status = CaseStatus::XPASS; - cr.strict_xpass = strict; - cr.out = detail::fmt_xpass(tc, ms, why, opt.tap_output, - opt.color_output && !detail::env_no_color(), - strict, tap_index); - } - return cr; - } - - // Normal pass/fail - if (ctx.failed) { - cr.status = CaseStatus::FAIL; - cr.msg = ctx.message; - if (opt.tap_output) { - std::ostringstream oss; - oss << "not ok " << tap_index << " - " << tc.suite << "." << tc.name - << " [" << tc.tag << "] (" << std::fixed << std::setprecision(3) << ms << " ms)\n"; - oss << " ---\n file: " << tc.file << "\n line: " << tc.line - << "\n msg: " << ctx.message << "\n ...\n"; - cr.out = oss.str(); - } else { - cr.out = detail::fmt_fail(tc, ms, ctx, /*tap*/false, - opt.color_output && !detail::env_no_color()); - } - } else { - cr.status = CaseStatus::PASS; - cr.out = opt.tap_output - ? ([&]{ - std::ostringstream oss; - oss << "ok " << tap_index << " - " << tc.suite << "." << tc.name - << " [" << tc.tag << "] (" << std::fixed << std::setprecision(3) << ms << " ms)\n"; - return oss.str(); - })() - : detail::fmt_pass(tc, ms, /*tap*/false, - opt.color_output && !detail::env_no_color()); - } - return cr; -} - -// Worker functor for parallel execution (no lambdas) -struct Worker { - const std::vector* selected; - const std::vector* cases; - const RunOptions* opt; - std::vector* results; - std::atomic* next_index; - - Worker(const std::vector* sel, - const std::vector* rcases, - const RunOptions* ropt, - std::vector* res, - std::atomic* next) - : selected(sel), cases(rcases), opt(ropt), results(res), next_index(next) {} - - void operator()() { - while (true) { - size_t k = next_index->fetch_add(1); - if (k >= selected->size()) break; - int idx = (*selected)[k]; - (*results)[k] = run_one((*cases)[idx], *opt, (int)k + 1); - } - } -}; - -inline RunResult run_all(const RunOptions& opt = {}) { - RunResult rr{}; - const auto& r = registry(); - - // Collect selected cases - std::vector selected; - selected.reserve(r.size()); - for (int i = 0; i < (int)r.size(); ++i) { - if (match_filter(r[i], opt)) selected.push_back(i); - } - - if (opt.tap_output) { - std::printf("TAP version 13\n"); - std::printf("1..%zu\n", selected.size()); - } - - // Results store in deterministic order - std::vector results(selected.size()); - - // If stop_on_fail or threads <= 1, run sequential - if (opt.stop_on_fail || opt.threads <= 1) { - for (size_t k = 0; k < selected.size(); ++k) { - int idx = selected[k]; - auto cr = run_one(r[idx], opt, (int)k + 1); - results[k] = std::move(cr); - rr.total++; - rr.ms_total += results[k].ms; - - switch (results[k].status) { - case CaseStatus::PASS: rr.passed++; break; - case CaseStatus::FAIL: rr.failed++; break; - case CaseStatus::SKIP: rr.skipped++; break; - case CaseStatus::XFAIL: rr.xfailed++; break; - case CaseStatus::XPASS: - if (results[k].strict_xpass) rr.failed++; - else rr.xpassed++; - break; - } - - if (!opt.summary_only) { - std::fwrite(results[k].out.data(), 1, results[k].out.size(), stdout); - } - if (opt.stop_on_fail && results[k].status == CaseStatus::FAIL) break; - } - } else { - // Parallel execution with deterministic output ordering - std::atomic next{0}; - int threads = std::max(1, opt.threads); - std::vector workers; - workers.reserve(threads); - - for (int t = 0; t < threads; ++t) { - workers.emplace_back(Worker(&selected, &r, &opt, &results, &next)); - } - for (std::thread& th : workers) th.join(); - - // Aggregate + print in order - for (size_t k = 0; k < results.size(); ++k) { - rr.total++; - rr.ms_total += results[k].ms; - switch (results[k].status) { - case CaseStatus::PASS: rr.passed++; break; - case CaseStatus::FAIL: rr.failed++; break; - case CaseStatus::SKIP: rr.skipped++; break; - case CaseStatus::XFAIL: rr.xfailed++; break; - case CaseStatus::XPASS: - if (results[k].strict_xpass) rr.failed++; - else rr.xpassed++; - break; - } - if (!opt.summary_only) { - std::fwrite(results[k].out.data(), 1, results[k].out.size(), stdout); - } - } - } - - if (!opt.summary_only) { - bool all_ok = (rr.failed == 0); - if (opt.color_output && !detail::env_no_color()) { - const char* col = all_ok ? "\x1b[32m" : "\x1b[31m"; - std::printf( - "\n%sSummary:%s total=%d, passed=%d, failed=%d, skipped=%d, xfail=%d, xpass=%d, time=%.3f ms\n", - col, "\x1b[0m", rr.total, rr.passed, rr.failed, rr.skipped, rr.xfailed, rr.xpassed, rr.ms_total); - } else { - std::printf( - "\nSummary: total=%d, passed=%d, failed=%d, skipped=%d, xfail=%d, xpass=%d, time=%.3f ms\n", - rr.total, rr.passed, rr.failed, rr.skipped, rr.xfailed, rr.xpassed, rr.ms_total); - } - } - - // -------- Reports -------- - if (opt.report_json || opt.report_junit || opt.report_md) { - // Prepare linear arrays for writers - std::vector statuses(selected.size()); - std::vector durations(selected.size()); - std::vector reasons(selected.size()); - std::vector messages(selected.size()); - for (size_t i = 0; i < selected.size(); ++i) { - statuses[i] = results[i].status; - durations[i] = results[i].ms; - reasons[i] = results[i].reason; - messages[i] = results[i].msg; - } - if (opt.report_json) { - detail::write_json_report(opt.report_json, selected, r, - statuses, durations, reasons, messages, rr); - } - if (opt.report_junit) { - detail::write_junit_report(opt.report_junit, selected, r, - statuses, durations, reasons, messages, rr, - opt.junit_xfail_as_skip); - } - if (opt.report_md) { - detail::write_markdown_report(opt.report_md, selected, r, - statuses, durations, reasons, messages, rr); - } - } - - return rr; -} - -// -------- Report writers (implementations placed after RunResult is defined) -------- -namespace detail { - -inline const char* status_to_str(CaseStatus s) { - switch (s) { - case CaseStatus::PASS: return "pass"; - case CaseStatus::FAIL: return "fail"; - case CaseStatus::SKIP: return "skip"; - case CaseStatus::XFAIL: return "xfail"; - case CaseStatus::XPASS: return "xpass"; - } - return "unknown"; -} - -inline std::string escape_xml(const std::string& s) { - std::ostringstream o; - for (char c : s) { - switch (c) { - case '&': o << "&"; break; - case '<': o << "<"; break; - case '>': o << ">"; break; - case '"': o << """;break; - case '\'':o << "'";break; - default: o << c; break; - } - } - return o.str(); -} - -inline std::string json_escape(const std::string& s) { - std::ostringstream o; - for (char c : s) { - switch (c) { - case '"': o << "\\\""; break; - case '\\': o << "\\\\"; break; - case '\n': o << "\\n"; break; - case '\r': o << "\\r"; break; - case '\t': o << "\\t"; break; - default: o << c; break; - } - } - return o.str(); -} - -inline void write_json_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary) -{ - if (!path) return; - std::ofstream f(path); - if (!f.is_open()) return; - - f << "{\n"; - f << " \"summary\": {" - << "\"total\": " << summary.total << ", " - << "\"passed\": " << summary.passed << ", " - << "\"failed\": " << summary.failed << ", " - << "\"skipped\": " << summary.skipped << ", " - << "\"xfail\": " << summary.xfailed << ", " - << "\"xpass\": " << summary.xpassed << ", " - << "\"time_ms\": " << std::fixed << std::setprecision(3) << summary.ms_total - << "},\n"; - f << " \"tests\": [\n"; - for (size_t k = 0; k < selected.size(); ++k) { - const TestCase& tc = cases[selected[k]]; - f << " {\n"; - f << " \"suite\": \"" << tc.suite << "\",\n"; - f << " \"name\": \"" << tc.name << "\",\n"; - f << " \"tag\": \"" << tc.tag << "\",\n"; - f << " \"file\": \"" << tc.file << "\",\n"; - f << " \"line\": " << tc.line << ",\n"; - f << " \"status\": \""<< status_to_str(statuses[k]) << "\",\n"; - f << " \"duration_ms\": " << std::fixed << std::setprecision(3) << durations[k] << ",\n"; - - f << " \"reason\": " << (reasons[k].empty() ? "null" : ("\"" + json_escape(reasons[k]) + "\"")) << ",\n"; - f << " \"message\": " << (messages[k].empty() ? "null" : ("\"" + json_escape(messages[k]) + "\"")) << "\n"; - - f << " }" << (k + 1 < selected.size() ? "," : "") << "\n"; - } - f << " ]\n"; - f << "}\n"; -} - -inline void write_junit_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary, - bool xfail_as_skip) -{ - if (!path) return; - std::ofstream f(path); - if (!f.is_open()) return; - - // Group by suite for multiple - std::map> by_suite; - for (size_t i = 0; i < selected.size(); ++i) { - by_suite[cases[selected[i]].suite].push_back(i); - } - - f << "\n"; - f << "\n"; - - for (const auto& kv : by_suite) { - const std::string& suite = kv.first; - const auto& idxs = kv.second; - - // per-suite stats - int tests=0, failures=0, skipped=0; - double time_sec = 0.0; - for (size_t i : idxs) { - ++tests; - time_sec += durations[i] / 1000.0; - const auto st = statuses[i]; - if (st == CaseStatus::FAIL) failures++; - if (st == CaseStatus::SKIP) skipped++; - if (st == CaseStatus::XFAIL && xfail_as_skip) skipped++; - if (st == CaseStatus::XPASS) { - // XPASS(strict) has been counted as failure in summary already - } - } - - f << " \n"; - - for (size_t i : idxs) { - const TestCase& tc = cases[selected[i]]; - const auto& st = statuses[i]; - const auto& dur = durations[i]; - const auto& rsn = reasons[i]; - const auto& msg = messages[i]; - - f << " "; - - if (st == CaseStatus::FAIL) { - f << "\n " - << escape_xml(tc.file) << ":" << tc.line << "\n"; - } else if (st == CaseStatus::SKIP) { - f << "\n \n"; - } else if (st == CaseStatus::XFAIL) { - if (xfail_as_skip) { - f << "\n \n"; - } else { - f << "\n \n"; - } - } - f << " \n"; - } - f << " \n"; - } - f << "\n"; -} - -inline void write_markdown_report( - const char* path, - const std::vector& selected, - const std::vector& cases, - const std::vector& statuses, - const std::vector& durations, - const std::vector& reasons, - const std::vector& messages, - const ::mtest::RunResult& summary) -{ - if (!path) return; - std::ofstream f(path); - if (!f.is_open()) return; - - f << "# Mini.hpp Test Report\n\n"; - f << "**Summary** \n"; - f << "- Total: " << summary.total << "\n"; - f << "- Passed: " << summary.passed << "\n"; - f << "- Failed: " << summary.failed << "\n"; - f << "- Skipped: " << summary.skipped << "\n"; - f << "- XFAIL: " << summary.xfailed << "\n"; - f << "- XPASS: " << summary.xpassed << "\n"; - f << "- Time: " << std::fixed << std::setprecision(3) << summary.ms_total << " ms\n\n"; - - // Group by suite for readability - std::map> by_suite; - for (size_t i = 0; i < selected.size(); ++i) { - by_suite[cases[selected[i]].suite].push_back(i); - } - - for (const auto& kv : by_suite) { - f << "## Suite: " << kv.first << "\n\n"; - for (size_t i : kv.second) { - const TestCase& tc = cases[selected[i]]; - f << "### " << tc.name << " \n"; - f << "- **Tag**: " << tc.tag << " \n"; - f << "- **Status**: " << status_to_str(statuses[i]) << " \n"; - f << "- **Duration**: " << std::fixed << std::setprecision(3) << durations[i] << " ms \n"; - f << "- **Location**: " << tc.file << ":" << tc.line << " \n"; - if (!reasons[i].empty()) f << "- **Reason**: " << reasons[i] << " \n"; - if (!messages[i].empty()) f << "- **Message**: " << messages[i] << " \n"; - f << "\n"; - } - } -} - -} // namespace detail - - - -inline int run_main(int argc, const char* argv[]) { - RunOptions opt{}; - for (int i = 1; i < argc; ++i) { - const char* a = argv[i]; - // Existing CLI parsing: - if (std::strncmp(a, "--filter=", 9) == 0) opt.name_contains = a + 9; - else if (std::strncmp(a, "--tag=", 6) == 0) opt.tag_equals = a + 6; - else if (std::strcmp(a, "--tap") == 0) opt.tap_output = true; - else if (std::strcmp(a, "--stop-on-fail") == 0) opt.stop_on_fail = true; - else if (std::strcmp(a, "--summary") == 0) opt.summary_only = true; - else if (std::strcmp(a, "--no-tap") == 0) opt.tap_output = false; - else if (std::strcmp(a, "--no-color") == 0) opt.color_output = false; - else if (std::strcmp(a, "--color") == 0) opt.color_output = true; - else if (std::strncmp(a, "--threads=", 10) == 0) { - int v = std::atoi(a + 10); - if (v > 0) opt.threads = v; - } - else if (std::strcmp(a, "--xfail-strict") == 0) { - opt.xfail_strict = true; - } - else if (std::strncmp(a, "--report-json=", 14) == 0) { - opt.report_json = a + 14; - } - else if (std::strncmp(a, "--report-junit=", 15) == 0) { - opt.report_junit = a + 15; - } - else if (std::strncmp(a, "--report-md=", 12) == 0) { - opt.report_md = a + 12; - } - else if (std::strcmp(a, "--junit-xfail-as-failure") == 0) { - opt.junit_xfail_as_skip = false; - } - } - - // Add auto-report defaults here: - if (!opt.report_json && !opt.report_junit && !opt.report_md) { - const char* dir = std::getenv("TEST_REPORT_DIR"); - std::string outdir = dir ? dir : "."; // fallback to current dir - - const char* exe = (argc > 0 && argv[0]) ? argv[0] : "mini"; - const char* base = std::strrchr(exe, '/'); -#ifdef _WIN32 - const char* back = std::strrchr(exe, '\\'); - if (back && (!base || back > base)) base = back; -#endif - base = base ? (base + 1) : exe; - - static std::string json = outdir + "/" + std::string(base) + ".json"; - static std::string junit = outdir + "/" + std::string(base) + ".junit.xml"; - static std::string md = outdir + "/" + std::string(base) + ".md"; - - opt.report_json = json.c_str(); - opt.report_junit = junit.c_str(); - opt.report_md = md.c_str(); - } - - auto rr = run_all(opt); - return rr.failed ? 1 : 0; -} - -} // namespace mtest - - -// Global main wrapper (can be disabled with -DMTEST_NO_MAIN) -#ifndef MTEST_NO_MAIN -int main(int argc, const char* argv[]) { - return mtest::run_main(argc, argv); -} -#endif -#endif diff --git a/tests/unit/test_getResConf.cpp b/tests/unit/test_getResConf.cpp deleted file mode 100644 index caead9059..000000000 --- a/tests/unit/test_getResConf.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -// SPDX-License-Identifier: BSD-3-Clause-Clear - -// tests/unit/test_getResConf.cpp -// -// Unit tests for ResourceRegistry::getResConf(uint32_t resourceId) -// -// Covered cases: -// 1) Unknown ID returns nullptr -// 2) Known ID (isBuSpecified=false) returns the same ResConfInfo* -// 3) Known ID (isBuSpecified=true) returns the same ResConfInfo* -// (key currently uses MSB) -// -// Implementation detail in current code (ResourceRegistry.cpp): -// - registerResource() stores mapping under either (ResType<<16) or, when -// isBuSpecified=true, under (1u<<31). It does NOT OR ResID currently. -// To keep tests robust, we probe keys using getResourceTableIndex() and -// then validate getResConf() with whatever key is actually used. - -#define MTEST_NO_MAIN 1 -#include "../framework/mini.h" -#include -#include -#include - -// Production header -#include "../../resource-tuner/core/Include/ResourceRegistry.h" - -// ---- Helpers ---- -static std::string make_temp_file_with(const std::string& contents) { - std::string path = - "/tmp/restune_rr_conf_" + std::to_string(reinterpret_cast(&errno)) + ".txt"; - std::ofstream f(path); - f << contents << std::endl; - f.close(); - return path; -} - -// allocate & minimally fill a ResConfInfo (registry takes ownership) -static ResConfInfo* make_minimal_conf(uint8_t resType, - uint16_t resId, - const std::string& resourcePath, - ResourceApplyType applyType = APPLY_GLOBAL) { - auto* r = new ResConfInfo{}; - r->mResourceName = "UT_" + std::to_string(resType) + "_" + std::to_string(resId); - r->mResourcePath = resourcePath; - r->mResourceResType = resType; // must be non-zero - r->mResourceResID = resId; - r->mHighThreshold = -1; - r->mLowThreshold = -1; - r->mPermissions = PERMISSION_THIRD_PARTY; - r->mModes = 0; - r->mApplyType = applyType; - r->mPolicy = LAZY_APPLY; - r->mResourceApplierCallback = nullptr; - r->mUnit = U_NA; - r->mResourceTearCallback = nullptr; - return r; -} - -// Try all plausible keys and return the first that exists in the table. -// Order: (type<<16)|id -> (type<<16) -> (1u<<31) if buKeyAllowed - -static bool resolve_key(ResourceRegistry* rr, - uint8_t resType, - uint16_t resId, - bool buKeyAllowed, - uint32_t& outKey) { - const uint32_t typeKey = (static_cast(resType) << 16); - const uint32_t comboKey = typeKey | static_cast(resId); - const uint32_t resIdOnly = static_cast(resId); - const uint32_t buKey = (1u << 31); - - if (rr->getResourceTableIndex(comboKey) != -1) { outKey = comboKey; return true; } - if (rr->getResourceTableIndex(typeKey) != -1) { outKey = typeKey; return true; } - if (rr->getResourceTableIndex(resIdOnly) != -1) { outKey = resIdOnly; return true; } - if (buKeyAllowed && rr->getResourceTableIndex(buKey) != -1) { - outKey = buKey; return true; - } - return false; -} - - -// ---------------------- Tests ---------------------- - -/** - * @test getResConf returns nullptr for an unknown key. - */ -MT_TEST(unit, UnknownId_ReturnsNull, "resourceregistry") { - auto rr = ResourceRegistry::getInstance(); - const uint32_t unknownId = 0xDEAD0000u; - - ResConfInfo* res = rr->getResConf(unknownId); - MT_REQUIRE_EQ(ctx, res == nullptr, true); -} - -/** - * @test Register with isBuSpecified=false, resolve actual key, and ensure - * getResConf() returns the exact stored pointer. - */ -MT_TEST(ResourceRegistry_GetResConf, KnownId_DefaultKey, "unit") { - auto rr = ResourceRegistry::getInstance(); - - const std::string tmp = make_temp_file_with("42"); - constexpr uint8_t kResType = 0x12; - constexpr uint16_t kResId = 0x3456; - ResConfInfo* toRegister = make_minimal_conf(kResType, kResId, tmp, APPLY_GLOBAL); - - rr->registerResource(toRegister, /*isBuSpecified=*/false); - - uint32_t key = 0; - bool found = resolve_key(rr.get(), kResType, kResId, /*buKeyAllowed*/false, key); - MT_REQUIRE_EQ(ctx, found, true); - - ResConfInfo* out = rr->getResConf(key); - MT_REQUIRE_EQ(ctx, out != nullptr, true); - MT_REQUIRE_EQ(ctx, out, toRegister); - MT_REQUIRE_EQ(ctx, out->mResourceResType, kResType); - MT_REQUIRE_EQ(ctx, out->mResourceResID, kResId); -} - -/** - * @test Register with isBuSpecified=true, resolve MSB (or fallback) key and ensure - * getResConf() returns the exact stored pointer. - */ - -// --- replace the body of KnownId_BuKeyMSB with this --- -MT_TEST(unit, KnownId_BuKeyMSB, "resourceregistry") { - auto rr = ResourceRegistry::getInstance(); - - const int beforeCount = rr->getTotalResourcesCount(); - - const std::string tmp = make_temp_file_with("defval"); - constexpr uint8_t kResType = 0x21; - constexpr uint16_t kResId = 0x0001; - ResConfInfo* toRegister = make_minimal_conf(kResType, kResId, tmp, APPLY_GLOBAL); - - rr->registerResource(toRegister, /*isBuSpecified=*/true); - - // Ensure registration happened - MT_REQUIRE_EQ(ctx, rr->getTotalResourcesCount(), beforeCount + 1); - - // Resolve whichever key the implementation actually used - uint32_t key = 0; - bool found = resolve_key(rr.get(), kResType, kResId, /*buKeyAllowed*/true, key); - - if (!found) { - // As a fallback, still validate pointer presence in the registry vector (identity check) - bool present = false; - for (ResConfInfo* p : rr->getRegisteredResources()) { - if (p == toRegister) { present = true; break; } - } - MT_REQUIRE_EQ(ctx, present, true); - // Mark as expected skip if no mapping key was discoverable (keeps CI green but signals behavior) - MT_SKIP(ctx, "Mapping key not discoverable in this build; registration and identity validated."); - return; - } - - ResConfInfo* out = rr->getResConf(key); - MT_REQUIRE_EQ(ctx, out != nullptr, true); - MT_REQUIRE_EQ(ctx, out, toRegister); - MT_REQUIRE_EQ(ctx, out->mResourceResType, kResType); - MT_REQUIRE_EQ(ctx, out->mResourceResID, kResId); -} - diff --git a/tests/unit/test_submitResProvisionRequest.cpp b/tests/unit/test_submitResProvisionRequest.cpp deleted file mode 100644 index 1c3c96c4d..000000000 --- a/tests/unit/test_submitResProvisionRequest.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -// SPDX-License-Identifier: BSD-3-Clause-Clear - -// (Optional) If you use a dedicated main TU, add this at top: - -#define MTEST_NO_MAIN 1 -#include "../framework/mini.h" -#include - -struct Request; -// (We will implement submitResProvisionRequest in a shim TU) -void submitResProvisionRequest(Request* request, int8_t isValidated); - -// ------ Mock & bookkeeping (GLOBAL SCOPE) ------ -struct RecordedCall { - Request* req{}; - int8_t flag{}; - int times{}; -}; -static RecordedCall g_lastCall; - -static void resetRecordedCall() { g_lastCall = RecordedCall{}; } - -// Move this OUT of the anonymous namespace so it has external linkage -void __mock_processIncomingRequest(Request* request, int8_t isValidated) { - g_lastCall.req = request; - g_lastCall.flag = isValidated; - g_lastCall.times += 1; -} - -namespace { - inline Request* makeNonNullRequestPtr() { - static int stub; - return reinterpret_cast(&stub); - } -} - -// ---------------------- Tests ---------------------- - -MT_TEST(unit, Forwards_IsValidated_False, "restune") { - resetRecordedCall(); - Request* req = makeNonNullRequestPtr(); - - submitResProvisionRequest(req, /*isValidated*/ 0); - - MT_REQUIRE_EQ(ctx, g_lastCall.times, 1); - MT_REQUIRE_EQ(ctx, g_lastCall.req, req); - MT_REQUIRE_EQ(ctx, g_lastCall.flag, 0); -} - -MT_TEST(unit, Forwards_IsValidated_True, "restune") { - resetRecordedCall(); - Request* req = makeNonNullRequestPtr(); - - submitResProvisionRequest(req, /*isValidated*/ 1); - - MT_REQUIRE_EQ(ctx, g_lastCall.times, 1); - MT_REQUIRE_EQ(ctx, g_lastCall.req, req); - MT_REQUIRE_EQ(ctx, g_lastCall.flag, 1); -} - -MT_TEST(unit, Forwards_Null_Request_Pointer, "restune") { - resetRecordedCall(); - - submitResProvisionRequest(nullptr, /*isValidated*/ 1); - - MT_REQUIRE_EQ(ctx, g_lastCall.times, 1); - MT_REQUIRE_EQ(ctx, g_lastCall.req, static_cast(nullptr)); - MT_REQUIRE_EQ(ctx, g_lastCall.flag, 1); -} -