package com.htopweb.system; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; class SystemProcessesServiceTest { @Test void parseProcessStat_lineWithParentheses() throws IOException { // Simulate a /proc/[pid]/stat line // pid (comm) state ppid pgrp session tty_nr tpgid flags minflt cminflt majflt cmajflt // utime stime cutime cstime priority nice num_threads itrealvalue starttime vsize rss ... String stat = "1234 (myprocess) S 1 1234 1234 0 -1 4194304 100 0 0 0 500 300 0 0 20 0 1 0 50 1048576 256 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; int open = stat.indexOf('('); int close = stat.lastIndexOf(')'); assertTrue(open >= 0); assertTrue(close > open); String name = stat.substring(open + 1, close); assertEquals("myprocess", name); String[] fields = stat.substring(close + 2).trim().split("\\s+"); // fields[0] = state (field 3) // fields[11] = utime (field 14) // fields[12] = stime (field 15) // fields[19] = starttime (field 22) // fields[20] = vsize (field 23) // fields[21] = rss (field 24) assertTrue(fields.length >= 22); assertEquals("S", fields[0]); assertEquals(500, Long.parseLong(fields[11])); assertEquals(300, Long.parseLong(fields[12])); } @Test void calculateCpuUsage_deltaTicks() { long prevTicks = 1000L; long currTicks = 1500L; long clockTicksPerSecond = 100L; double elapsedSeconds = 5.0; int logicalProcessorCount = 4; long deltaTicks = currTicks - prevTicks; double cpuUsage = deltaTicks / (double) clockTicksPerSecond / elapsedSeconds * 100.0 / logicalProcessorCount; // 500 ticks / 100 Hz / 5s * 100 / 4 cores = 25% assertEquals(25.0, cpuUsage, 0.01); } @Test void calculateCpuUsage_clampedTo100() { double rawUsage = 150.0; double clamped = Math.max(0.0, Math.min(100.0, rawUsage)); assertEquals(100.0, clamped, 0.01); } @Test void calculateCpuUsage_negativeDeltaIgnored() { long prevTicks = 2000L; long currTicks = 1500L; long deltaTicks = currTicks - prevTicks; // Negative delta should be ignored if (deltaTicks >= 0) { fail("Delta should be negative"); } } @Test void parseBootTime_fromStatFile() throws IOException { Path tempDir = Files.createTempDirectory("test"); String stat = "btime 1700000000\n" + "procs 245\n"; Files.writeString(tempDir.resolve("stat"), stat); long bootTime = 0L; for (String line : Files.readAllLines(tempDir.resolve("stat"))) { if (line.startsWith("btime ")) { bootTime = Long.parseLong(line.substring(6).trim()); } } assertEquals(1700000000L, bootTime); // Cleanup Files.delete(tempDir.resolve("stat")); Files.delete(tempDir); } @Test void parseThreadCount_fromStatusFile() throws IOException { Path tempDir = Files.createTempDirectory("test"); String status = "Name:\tmyprocess\n" + "Umask:\t0022\n" + "State:\tS (sleeping)\n" + "Tgid:\t1234\n" + "Ngid:\t0\n" + "Pid:\t1234\n" + "PPid:\t1\n" + "Threads:\t8\n"; Files.writeString(tempDir.resolve("status"), status); int threadCount = 0; for (String line : Files.readAllLines(tempDir.resolve("status"))) { if (line.startsWith("Threads:")) { threadCount = Integer.parseInt( line.substring("Threads:".length()).trim() ); } } assertEquals(8, threadCount); // Cleanup Files.delete(tempDir.resolve("status")); Files.delete(tempDir); } @Test void processSnapshot_calculatesUptime() { long bootTimeSeconds = 1700000000L; long startTicks = 50000L; long clockTicksPerSecond = 100L; long currentTimeSeconds = 1700010000L; // 10000 seconds after epoch long processStartSeconds = bootTimeSeconds + startTicks / clockTicksPerSecond; long uptimeSeconds = Math.max(0L, currentTimeSeconds - processStartSeconds); // 1700000000 + 500 = 1700000500 // 1700010000 - 1700000500 = 9500 assertEquals(9500L, uptimeSeconds); } @Test void processSnapshot_residentMemoryInBytes() { long residentPages = 256L; long pageSizeBytes = 4096L; long residentBytes = residentPages * pageSizeBytes; assertEquals(1048576L, residentBytes); // 1 MB } }