#include #include #include #include #include #include #include #include #include #include #include #include extern char** environ; // Spawn a child process and return its PID (0 on failure). static pid_t spawnProcess(const char* path, char* const argv[]) { pid_t pid = 0; posix_spawnattr_t attr; posix_spawnattr_init(&attr); int rc = posix_spawn(&pid, path, nullptr, &attr, const_cast(argv), environ); posix_spawnattr_destroy(&attr); return (rc == 0) ? pid : 0; } // Kill a child process and reap it. static void killProcess(pid_t pid) { if (pid <= 0) return; kill(pid, SIGTERM); int status; waitpid(pid, &status, 0); } class ProcessStatsTest : public ::testing::Test { protected: std::vector m_processes; void SetUp() override { ProcessStats::clearHistory(); } void TearDown() override { ProcessStats::clearHistory(); for (pid_t pid : m_processes) { killProcess(pid); } m_processes.clear(); } }; // ============================================================================= // getProcessStats Tests // ============================================================================= TEST_F(ProcessStatsTest, GetProcessStats_ReturnsZeroedStatsForNegativePid) { ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(-1); EXPECT_EQ(stats.cpuPercent, 0.0); EXPECT_EQ(stats.cpuTimeSeconds, 0.0); EXPECT_EQ(stats.memoryMB, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_ReturnsZeroedStatsForZeroPid) { ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(0); EXPECT_EQ(stats.cpuPercent, 0.0); EXPECT_EQ(stats.cpuTimeSeconds, 0.0); EXPECT_EQ(stats.memoryMB, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_ReturnsValidStatsForCurrentProcess) { int64_t currentPid = static_cast(getpid()); ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid); EXPECT_GT(stats.memoryMB, 0.0); EXPECT_GE(stats.cpuTimeSeconds, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_MemoryIsNonNegative) { int64_t currentPid = static_cast(getpid()); ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid); EXPECT_GE(stats.memoryMB, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_CpuTimeIsNonNegative) { int64_t currentPid = static_cast(getpid()); ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid); EXPECT_GE(stats.cpuTimeSeconds, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_CpuPercentIsZeroOnFirstCall) { int64_t currentPid = static_cast(getpid()); ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid); EXPECT_EQ(stats.cpuPercent, 0.0); } TEST_F(ProcessStatsTest, GetProcessStats_CpuPercentUpdatesOnSecondCall) { int64_t currentPid = static_cast(getpid()); ProcessStats::getProcessStats(currentPid); volatile double sum = 0.0; for (int i = 0; i < 1000000; ++i) { sum += i * 0.1; } usleep(10000); // 10ms ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid); EXPECT_GE(stats.cpuPercent, 0.0); } // ============================================================================= // getModuleStats Tests // ============================================================================= TEST_F(ProcessStatsTest, GetModuleStats_ReturnsEmptyArrayWhenNoPlugins) { std::unordered_map processes; char* result = ProcessStats::getModuleStats(processes); ASSERT_NE(result, nullptr); nlohmann::json doc = nlohmann::json::parse(result); EXPECT_TRUE(doc.is_array()); EXPECT_EQ(doc.size(), 0); delete[] result; } TEST_F(ProcessStatsTest, GetModuleStats_ReturnsNonNullPointer) { std::unordered_map processes; char* result = ProcessStats::getModuleStats(processes); ASSERT_NE(result, nullptr); delete[] result; } TEST_F(ProcessStatsTest, GetModuleStats_ReturnsValidJsonStructure) { // Spawn a real "sleep 2" child process so we have a valid PID. // Use posix_spawnp to search PATH (nix sandbox has no /bin/sleep). char* argv[] = {(char*)"sleep", (char*)"2", nullptr}; pid_t pid = 0; posix_spawnattr_t attr; posix_spawnattr_init(&attr); int rc = posix_spawnp(&pid, "sleep", nullptr, &attr, argv, environ); posix_spawnattr_destroy(&attr); ASSERT_EQ(rc, 0) << "Failed to spawn sleep process: " << strerror(rc); m_processes.push_back(pid); std::unordered_map processes; processes.emplace("test_plugin", static_cast(pid)); char* result = ProcessStats::getModuleStats(processes); ASSERT_NE(result, nullptr); nlohmann::json doc = nlohmann::json::parse(result); EXPECT_TRUE(doc.is_array()); ASSERT_EQ(doc.size(), 1); auto moduleObj = doc[0]; EXPECT_TRUE(moduleObj.contains("name")); EXPECT_TRUE(moduleObj.contains("cpu_percent")); EXPECT_TRUE(moduleObj.contains("cpu_time_seconds")); EXPECT_TRUE(moduleObj.contains("memory_mb")); EXPECT_EQ(moduleObj["name"].get(), "test_plugin"); EXPECT_GE(moduleObj["cpu_percent"].get(), 0.0); EXPECT_GE(moduleObj["cpu_time_seconds"].get(), 0.0); EXPECT_GE(moduleObj["memory_mb"].get(), 0.0); delete[] result; }