2026-01-22 15:51:03 -05:00
|
|
|
#include <gtest/gtest.h>
|
2026-03-24 16:50:23 -04:00
|
|
|
#include <process_stats/process_stats.h>
|
2026-03-27 12:33:38 -04:00
|
|
|
#include <nlohmann/json.hpp>
|
2026-01-22 15:51:03 -05:00
|
|
|
#include <cstring>
|
2026-04-09 10:30:33 -04:00
|
|
|
#include <string>
|
|
|
|
|
#include <unordered_map>
|
2026-04-09 15:38:57 -04:00
|
|
|
#include <vector>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <spawn.h>
|
|
|
|
|
#include <sys/wait.h>
|
2026-01-22 15:51:03 -05:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2026-04-09 15:38:57 -04:00
|
|
|
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<char* const*>(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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
class ProcessStatsTest : public ::testing::Test {
|
|
|
|
|
protected:
|
2026-04-09 15:38:57 -04:00
|
|
|
std::vector<pid_t> m_processes;
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
void SetUp() override {
|
2026-03-24 16:50:23 -04:00
|
|
|
ProcessStats::clearHistory();
|
2026-01-22 15:51:03 -05:00
|
|
|
}
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
void TearDown() override {
|
2026-03-24 16:50:23 -04:00
|
|
|
ProcessStats::clearHistory();
|
2026-04-09 15:38:57 -04:00
|
|
|
for (pid_t pid : m_processes) {
|
|
|
|
|
killProcess(pid);
|
2026-01-22 15:51:03 -05:00
|
|
|
}
|
2026-03-24 16:50:23 -04:00
|
|
|
m_processes.clear();
|
2026-01-22 15:51:03 -05:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// getProcessStats Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_ReturnsZeroedStatsForNegativePid) {
|
2026-03-24 16:50:23 -04:00
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(-1);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_EQ(stats.cpuPercent, 0.0);
|
|
|
|
|
EXPECT_EQ(stats.cpuTimeSeconds, 0.0);
|
|
|
|
|
EXPECT_EQ(stats.memoryMB, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_ReturnsZeroedStatsForZeroPid) {
|
2026-03-24 16:50:23 -04:00
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(0);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_EQ(stats.cpuPercent, 0.0);
|
|
|
|
|
EXPECT_EQ(stats.cpuTimeSeconds, 0.0);
|
|
|
|
|
EXPECT_EQ(stats.memoryMB, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_ReturnsValidStatsForCurrentProcess) {
|
2026-04-09 15:38:57 -04:00
|
|
|
int64_t currentPid = static_cast<int64_t>(getpid());
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_GT(stats.memoryMB, 0.0);
|
|
|
|
|
EXPECT_GE(stats.cpuTimeSeconds, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_MemoryIsNonNegative) {
|
2026-04-09 15:38:57 -04:00
|
|
|
int64_t currentPid = static_cast<int64_t>(getpid());
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_GE(stats.memoryMB, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_CpuTimeIsNonNegative) {
|
2026-04-09 15:38:57 -04:00
|
|
|
int64_t currentPid = static_cast<int64_t>(getpid());
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_GE(stats.cpuTimeSeconds, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_CpuPercentIsZeroOnFirstCall) {
|
2026-04-09 15:38:57 -04:00
|
|
|
int64_t currentPid = static_cast<int64_t>(getpid());
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_EQ(stats.cpuPercent, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetProcessStats_CpuPercentUpdatesOnSecondCall) {
|
2026-04-09 15:38:57 -04:00
|
|
|
int64_t currentPid = static_cast<int64_t>(getpid());
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
ProcessStats::getProcessStats(currentPid);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
volatile double sum = 0.0;
|
|
|
|
|
for (int i = 0; i < 1000000; ++i) {
|
|
|
|
|
sum += i * 0.1;
|
|
|
|
|
}
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
usleep(10000); // 10ms
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
ProcessStats::ProcessStatsData stats = ProcessStats::getProcessStats(currentPid);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_GE(stats.cpuPercent, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// getModuleStats Tests
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
2026-04-23 09:20:45 -04:00
|
|
|
TEST_F(ProcessStatsTest, GetModuleStats_ReturnsEmptyArrayWhenNoModules) {
|
2026-04-09 10:30:33 -04:00
|
|
|
std::unordered_map<std::string, int64_t> processes;
|
2026-03-24 16:50:23 -04:00
|
|
|
char* result = ProcessStats::getModuleStats(processes);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
ASSERT_NE(result, nullptr);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-03-27 12:33:38 -04:00
|
|
|
nlohmann::json doc = nlohmann::json::parse(result);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-03-27 12:33:38 -04:00
|
|
|
EXPECT_TRUE(doc.is_array());
|
|
|
|
|
EXPECT_EQ(doc.size(), 0);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetModuleStats_ReturnsNonNullPointer) {
|
2026-04-09 10:30:33 -04:00
|
|
|
std::unordered_map<std::string, int64_t> processes;
|
2026-03-24 16:50:23 -04:00
|
|
|
char* result = ProcessStats::getModuleStats(processes);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
ASSERT_NE(result, nullptr);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
delete[] result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(ProcessStatsTest, GetModuleStats_ReturnsValidJsonStructure) {
|
2026-04-13 14:55:38 +02:00
|
|
|
// 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).
|
2026-04-09 15:38:57 -04:00
|
|
|
char* argv[] = {(char*)"sleep", (char*)"2", nullptr};
|
2026-04-13 14:55:38 +02:00
|
|
|
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);
|
2026-04-09 15:38:57 -04:00
|
|
|
m_processes.push_back(pid);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-04-09 10:30:33 -04:00
|
|
|
std::unordered_map<std::string, int64_t> processes;
|
2026-04-23 09:20:45 -04:00
|
|
|
processes.emplace("test_module", static_cast<int64_t>(pid));
|
2026-03-24 16:50:23 -04:00
|
|
|
|
|
|
|
|
char* result = ProcessStats::getModuleStats(processes);
|
|
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
ASSERT_NE(result, nullptr);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-03-27 12:33:38 -04:00
|
|
|
nlohmann::json doc = nlohmann::json::parse(result);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-03-27 12:33:38 -04:00
|
|
|
EXPECT_TRUE(doc.is_array());
|
|
|
|
|
ASSERT_EQ(doc.size(), 1);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-03-27 12:33:38 -04:00
|
|
|
auto moduleObj = doc[0];
|
2026-01-22 15:51:03 -05:00
|
|
|
EXPECT_TRUE(moduleObj.contains("name"));
|
|
|
|
|
EXPECT_TRUE(moduleObj.contains("cpu_percent"));
|
|
|
|
|
EXPECT_TRUE(moduleObj.contains("cpu_time_seconds"));
|
|
|
|
|
EXPECT_TRUE(moduleObj.contains("memory_mb"));
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-04-23 09:20:45 -04:00
|
|
|
EXPECT_EQ(moduleObj["name"].get<std::string>(), "test_module");
|
2026-03-27 12:33:38 -04:00
|
|
|
EXPECT_GE(moduleObj["cpu_percent"].get<double>(), 0.0);
|
|
|
|
|
EXPECT_GE(moduleObj["cpu_time_seconds"].get<double>(), 0.0);
|
|
|
|
|
EXPECT_GE(moduleObj["memory_mb"].get<double>(), 0.0);
|
2026-03-24 16:50:23 -04:00
|
|
|
|
2026-01-22 15:51:03 -05:00
|
|
|
delete[] result;
|
|
|
|
|
}
|