1
2
3
4
5
6
7
8
9
10
11 package psiprobe.controllers.oshi;
12
13 import static org.junit.jupiter.api.Assertions.assertEquals;
14 import static org.junit.jupiter.api.Assertions.assertNotNull;
15 import static org.junit.jupiter.api.Assertions.assertTrue;
16 import static org.mockito.Mockito.mock;
17 import static org.mockito.Mockito.mockStatic;
18 import static org.mockito.Mockito.when;
19
20 import jakarta.servlet.http.HttpServletRequest;
21 import jakarta.servlet.http.HttpServletResponse;
22
23 import java.lang.reflect.Field;
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.junit.jupiter.api.BeforeEach;
29 import org.junit.jupiter.api.Test;
30 import org.mockito.MockedStatic;
31 import org.springframework.web.servlet.ModelAndView;
32
33 import oshi.hardware.Baseboard;
34 import oshi.hardware.CentralProcessor;
35 import oshi.hardware.CentralProcessor.PhysicalProcessor;
36 import oshi.hardware.ComputerSystem;
37 import oshi.hardware.Display;
38 import oshi.hardware.Firmware;
39 import oshi.hardware.GlobalMemory;
40 import oshi.hardware.GraphicsCard;
41 import oshi.hardware.HWDiskStore;
42 import oshi.hardware.HWPartition;
43 import oshi.hardware.LogicalVolumeGroup;
44 import oshi.hardware.NetworkIF;
45 import oshi.hardware.PhysicalMemory;
46 import oshi.hardware.PowerSource;
47 import oshi.hardware.Sensors;
48 import oshi.hardware.SoundCard;
49 import oshi.hardware.UsbDevice;
50 import oshi.hardware.VirtualMemory;
51 import oshi.software.os.FileSystem;
52 import oshi.software.os.InternetProtocolStats;
53 import oshi.software.os.NetworkParams;
54 import oshi.software.os.OSFileStore;
55 import oshi.software.os.OSProcess;
56 import oshi.software.os.OSService;
57 import oshi.software.os.OSSession;
58 import oshi.software.os.OperatingSystem;
59 import oshi.util.Util;
60
61
62
63
64 class OshiControllerTest {
65
66 @BeforeEach
67 void setUp() throws Exception {
68 Field oshiField = OshiController.class.getDeclaredField("oshi");
69 oshiField.setAccessible(true);
70 oshiField.set(null, new ArrayList<String>());
71 }
72
73 @SuppressWarnings("unchecked")
74 private List<String> getOshiData() throws Exception {
75 Field oshiField = OshiController.class.getDeclaredField("oshi");
76 oshiField.setAccessible(true);
77 return (List<String>) oshiField.get(null);
78 }
79
80 private void invokeStatic(String methodName, Class<?> parameterType, Object argument)
81 throws Exception {
82 Method method = OshiController.class.getDeclaredMethod(methodName, parameterType);
83 method.setAccessible(true);
84 method.invoke(null, argument);
85 }
86
87 @Test
88 void handleRequestInternalUsesCachedData() throws Exception {
89 getOshiData().add("cached-entry");
90
91 OshiController controller = new OshiController();
92 controller.setViewName("oshi");
93
94 HttpServletRequest request = mock(HttpServletRequest.class);
95 HttpServletResponse response = mock(HttpServletResponse.class);
96
97 ModelAndView mv = controller.handleRequestInternal(request, response);
98
99 assertEquals("oshi", mv.getViewName());
100 assertNotNull(mv.getModel().get("oshi"));
101 assertEquals(1, ((List<?>) mv.getModel().get("oshi")).size());
102 }
103
104 @Test
105 void printPowerSourcesHandlesEmptyAndPopulatedLists() throws Exception {
106 invokeStatic("printPowerSources", List.class, List.of());
107 assertTrue(getOshiData().get(0).contains("Unknown"));
108
109 PowerSource source = mock(PowerSource.class);
110 when(source.toString()).thenReturn("Battery-A");
111 invokeStatic("printPowerSources", List.class, List.of(source));
112 assertTrue(getOshiData().get(1).contains("Battery-A"));
113 }
114
115 @Test
116 void printNetworkInterfacesHandlesEmptyAndNonEmptyLists() throws Exception {
117 invokeStatic("printNetworkInterfaces", List.class, List.of());
118 assertTrue(getOshiData().get(0).contains("Unknown"));
119
120 NetworkIF networkIF = mock(NetworkIF.class);
121 when(networkIF.toString()).thenReturn("eth0");
122 invokeStatic("printNetworkInterfaces", List.class, List.of(networkIF));
123 assertTrue(getOshiData().get(1).contains("eth0"));
124 }
125
126 @Test
127 void printGraphicsCardsHandlesEmptyAndNonEmptyLists() throws Exception {
128 invokeStatic("printGraphicsCards", List.class, List.of());
129 assertTrue(getOshiData().contains(" None detected."));
130
131 GraphicsCard card = mock(GraphicsCard.class);
132 when(card.toString()).thenReturn("GPU-1");
133 invokeStatic("printGraphicsCards", List.class, List.of(card));
134 assertTrue(getOshiData().contains(" GPU-1"));
135 }
136
137 @Test
138 void printDisplaysUsbAndSoundCardsAddEntries() throws Exception {
139 Display display = mock(Display.class);
140 UsbDevice usbDevice = mock(UsbDevice.class);
141 SoundCard soundCard = mock(SoundCard.class);
142
143 when(display.toString()).thenReturn("Display-1");
144 when(usbDevice.toString()).thenReturn("USB-A");
145 when(soundCard.toString()).thenReturn("Sound-A");
146
147 invokeStatic("printDisplays", List.class, List.of(display));
148 invokeStatic("printUsbDevices", List.class, List.of(usbDevice));
149 invokeStatic("printSoundCards", List.class, List.of(soundCard));
150
151 List<String> data = getOshiData();
152 assertTrue(data.contains("Display-1"));
153 assertTrue(data.contains("USB-A"));
154 assertTrue(data.contains(" Sound-A"));
155 }
156
157 @Test
158 void printServicesIncludesRunningAndStoppedServices() throws Exception {
159 OperatingSystem os = mock(OperatingSystem.class);
160
161 OSService running = mock(OSService.class);
162 when(running.getState()).thenReturn(OSService.State.RUNNING);
163 when(running.getProcessID()).thenReturn(111);
164 when(running.getName()).thenReturn("svc-running");
165
166 OSService stopped = mock(OSService.class);
167 when(stopped.getState()).thenReturn(OSService.State.STOPPED);
168 when(stopped.getProcessID()).thenReturn(222);
169 when(stopped.getName()).thenReturn("svc-stopped");
170
171 when(os.getServices()).thenReturn(List.of(running, stopped));
172
173 invokeStatic("printServices", OperatingSystem.class, os);
174
175 String rendered = String.join("\n", getOshiData());
176 assertTrue(rendered.contains("svc-running"));
177 assertTrue(rendered.contains("svc-stopped"));
178 }
179
180 @Test
181 void printNetworkAndInternetProtocolStatsRenderValues() throws Exception {
182 NetworkParams networkParams = mock(NetworkParams.class);
183 when(networkParams.toString()).thenReturn("host=example");
184
185 InternetProtocolStats internetProtocolStats = mock(InternetProtocolStats.class);
186 when(internetProtocolStats.getTCPv4Stats())
187 .thenReturn(mock(InternetProtocolStats.TcpStats.class));
188 when(internetProtocolStats.getTCPv6Stats())
189 .thenReturn(mock(InternetProtocolStats.TcpStats.class));
190 when(internetProtocolStats.getUDPv4Stats())
191 .thenReturn(mock(InternetProtocolStats.UdpStats.class));
192 when(internetProtocolStats.getUDPv6Stats())
193 .thenReturn(mock(InternetProtocolStats.UdpStats.class));
194
195 invokeStatic("printNetworkParameters", NetworkParams.class, networkParams);
196 invokeStatic("printInternetProtocolStats", InternetProtocolStats.class, internetProtocolStats);
197
198 String rendered = String.join("\n", getOshiData());
199 assertTrue(rendered.contains("host=example"));
200 assertTrue(rendered.contains("Internet Protocol statistics:"));
201 assertTrue(rendered.contains("TCPv4"));
202 }
203
204 @Test
205 void printLogicalVolumeGroupsAndDisksRenderEntries() throws Exception {
206 LogicalVolumeGroup lvg = mock(LogicalVolumeGroup.class);
207 when(lvg.toString()).thenReturn("vg0");
208
209 invokeStatic("printLogicalVolumegroups", List.class, List.of(lvg));
210
211 HWDiskStore disk = mock(HWDiskStore.class);
212 HWPartition partition = mock(HWPartition.class);
213 when(disk.toString()).thenReturn("disk0");
214 when(disk.getPartitions()).thenReturn(List.of(partition));
215 when(partition.toString()).thenReturn("sda1");
216
217 invokeStatic("printDisks", List.class, List.of(disk));
218
219 String rendered = String.join("\n", getOshiData());
220 assertTrue(rendered.contains("Logical Volume Groups:"));
221 assertTrue(rendered.contains("vg0"));
222 assertTrue(rendered.contains("Disks:"));
223 assertTrue(rendered.contains("disk0"));
224 assertTrue(rendered.contains("sda1"));
225 }
226
227 @Test
228 void printFileSystemFormatsFileStoreAndDescriptors() throws Exception {
229 FileSystem fileSystem = mock(FileSystem.class);
230 OSFileStore storeWithoutLogical = mock(OSFileStore.class);
231 OSFileStore storeWithLogical = mock(OSFileStore.class);
232
233 when(fileSystem.getOpenFileDescriptors()).thenReturn(10L);
234 when(fileSystem.getMaxFileDescriptors()).thenReturn(100L);
235 when(fileSystem.getFileStores()).thenReturn(List.of(storeWithoutLogical, storeWithLogical));
236
237 when(storeWithoutLogical.getName()).thenReturn("store0");
238 when(storeWithoutLogical.getDescription()).thenReturn("");
239 when(storeWithoutLogical.getType()).thenReturn("ext4");
240 when(storeWithoutLogical.getUsableSpace()).thenReturn(50L);
241 when(storeWithoutLogical.getTotalSpace()).thenReturn(100L);
242 when(storeWithoutLogical.getFreeInodes()).thenReturn(20L);
243 when(storeWithoutLogical.getTotalInodes()).thenReturn(40L);
244 when(storeWithoutLogical.getVolume()).thenReturn("/dev/sda1");
245 when(storeWithoutLogical.getLogicalVolume()).thenReturn("");
246 when(storeWithoutLogical.getMount()).thenReturn("/");
247
248 when(storeWithLogical.getName()).thenReturn("store1");
249 when(storeWithLogical.getDescription()).thenReturn("data");
250 when(storeWithLogical.getType()).thenReturn("xfs");
251 when(storeWithLogical.getUsableSpace()).thenReturn(75L);
252 when(storeWithLogical.getTotalSpace()).thenReturn(100L);
253 when(storeWithLogical.getFreeInodes()).thenReturn(10L);
254 when(storeWithLogical.getTotalInodes()).thenReturn(20L);
255 when(storeWithLogical.getVolume()).thenReturn("/dev/mapper/vg-lv");
256 when(storeWithLogical.getLogicalVolume()).thenReturn("vg-lv");
257 when(storeWithLogical.getMount()).thenReturn("/data");
258
259 invokeStatic("printFileSystem", FileSystem.class, fileSystem);
260
261 String rendered = String.join("\n", getOshiData());
262 assertTrue(rendered.contains("File Descriptors: 10/100"));
263 assertTrue(rendered.contains("store0"));
264 assertTrue(rendered.contains("store1"));
265 assertTrue(rendered.contains("is mounted at /"));
266 assertTrue(rendered.contains("is mounted at /data"));
267 }
268
269 @Test
270 void printOperatingSystemRendersSessionsAndPermissionStatus() throws Exception {
271 OperatingSystem os = mock(OperatingSystem.class);
272 OSSession session = mock(OSSession.class);
273 when(session.toString()).thenReturn("user-session");
274
275 when(os.toString()).thenReturn("MyOS");
276 when(os.getSystemBootTime()).thenReturn(1L);
277 when(os.getSystemUptime()).thenReturn(10L);
278 when(os.getSessions()).thenReturn(List.of(session));
279 when(os.isElevated()).thenReturn(false, true);
280
281 invokeStatic("printOperatingSystem", OperatingSystem.class, os);
282 invokeStatic("printOperatingSystem", OperatingSystem.class, os);
283
284 String rendered = String.join("\n", getOshiData());
285 assertTrue(rendered.contains("MyOS"));
286 assertTrue(rendered.contains("user-session"));
287 assertTrue(rendered.contains("without elevated permissions"));
288 assertTrue(rendered.contains("with elevated permissions"));
289 }
290
291 @Test
292 void printComputerSystemProcessorMemoryAndSensorsRenderValues() throws Exception {
293 ComputerSystem computerSystem = mock(ComputerSystem.class);
294 Firmware firmware = mock(Firmware.class);
295 Baseboard baseboard = mock(Baseboard.class);
296 when(computerSystem.toString()).thenReturn("computer");
297 when(firmware.toString()).thenReturn("firmware");
298 when(baseboard.toString()).thenReturn("baseboard");
299 when(computerSystem.getFirmware()).thenReturn(firmware);
300 when(computerSystem.getBaseboard()).thenReturn(baseboard);
301 invokeStatic("printComputerSystem", ComputerSystem.class, computerSystem);
302
303 CentralProcessor processor = mock(CentralProcessor.class);
304 PhysicalProcessor physicalProcessor = mock(PhysicalProcessor.class);
305 when(processor.toString()).thenReturn("cpu");
306 when(processor.getPhysicalProcessors()).thenReturn(List.of(physicalProcessor));
307 when(processor.getPhysicalPackageCount()).thenReturn(2);
308 when(physicalProcessor.getPhysicalPackageNumber()).thenReturn(1);
309 when(physicalProcessor.getPhysicalProcessorNumber()).thenReturn(2);
310 when(physicalProcessor.getEfficiency()).thenReturn(3);
311 when(physicalProcessor.getIdString()).thenReturn("cpu-id");
312 invokeStatic("printProcessor", CentralProcessor.class, processor);
313
314 GlobalMemory memory = mock(GlobalMemory.class);
315 VirtualMemory virtualMemory = mock(VirtualMemory.class);
316 PhysicalMemory physicalMemory = mock(PhysicalMemory.class);
317 when(memory.toString()).thenReturn("memory");
318 when(virtualMemory.toString()).thenReturn("virtual-memory");
319 when(physicalMemory.toString()).thenReturn("dimm0");
320 when(memory.getVirtualMemory()).thenReturn(virtualMemory);
321 when(memory.getPhysicalMemory()).thenReturn(List.of(physicalMemory));
322 invokeStatic("printMemory", GlobalMemory.class, memory);
323
324 Sensors sensors = mock(Sensors.class);
325 when(sensors.toString()).thenReturn("sensor-data");
326 invokeStatic("printSensors", Sensors.class, sensors);
327
328 String rendered = String.join("\n", getOshiData());
329 assertTrue(rendered.contains("computer"));
330 assertTrue(rendered.contains("firmware"));
331 assertTrue(rendered.contains("baseboard"));
332 assertTrue(rendered.contains("cpu-id"));
333 assertTrue(rendered.contains("virtual-memory"));
334 assertTrue(rendered.contains("dimm0"));
335 assertTrue(rendered.contains("sensor-data"));
336 }
337
338 @Test
339 void printCpuRendersTickLoadAndFrequencyInformation() throws Exception {
340 CentralProcessor processor = mock(CentralProcessor.class);
341 CentralProcessor.ProcessorIdentifier identifier =
342 mock(CentralProcessor.ProcessorIdentifier.class);
343 long[] previousTicks = new long[] {100, 10, 20, 200, 5, 3, 2, 1};
344 long[] currentTicks = new long[] {150, 15, 30, 260, 8, 5, 4, 2};
345
346 when(processor.getContextSwitches()).thenReturn(123L);
347 when(processor.getInterrupts()).thenReturn(456L);
348 when(processor.getSystemCpuLoadTicks()).thenReturn(previousTicks, currentTicks);
349 when(processor.getSystemCpuLoadBetweenTicks(previousTicks)).thenReturn(0.25d);
350 when(processor.getSystemLoadAverage(3)).thenReturn(new double[] {1.0d, -1.0d, 3.0d});
351 when(processor.getProcessorCpuLoadTicks()).thenReturn(new long[][] {previousTicks});
352 when(processor.getProcessorCpuLoadBetweenTicks(new long[][] {previousTicks}))
353 .thenReturn(new double[] {0.5d, 0.25d});
354 when(processor.getProcessorIdentifier()).thenReturn(identifier);
355 when(identifier.getVendorFreq()).thenReturn(2_000_000_000L);
356 when(processor.getMaxFreq()).thenReturn(3_000_000_000L);
357 when(processor.getCurrentFreq()).thenReturn(new long[] {1_500_000_000L, 1_600_000_000L});
358
359 try (MockedStatic<Util> utilMock = mockStatic(Util.class)) {
360 invokeStatic("printCpu", CentralProcessor.class, processor);
361 utilMock.verify(() -> Util.sleep(1000));
362 }
363
364 String rendered = String.join("\n", getOshiData());
365 assertTrue(rendered.contains("Context Switches/Interrupts: 123 / 456"));
366 assertTrue(rendered.contains("CPU load: 25.0%"));
367 assertTrue(rendered.contains("CPU load averages: 1.00 N/A 3.00"));
368 assertTrue(rendered.contains("CPU load per processor: 50.0% 25.0%"));
369 assertTrue(rendered.contains("Vendor Frequency:"));
370 assertTrue(rendered.contains("Current Frequencies:"));
371 }
372
373 @Test
374 void printProcessesRendersProcessTableArgumentsAndEnvironment() throws Exception {
375 OperatingSystem operatingSystem = mock(OperatingSystem.class);
376 GlobalMemory memory = mock(GlobalMemory.class);
377 OSProcess currentProcess = mock(OSProcess.class);
378 OSProcess topProcess = mock(OSProcess.class);
379
380 when(operatingSystem.getProcessId()).thenReturn(42);
381 when(operatingSystem.getProcess(42)).thenReturn(currentProcess);
382 when(currentProcess.getProcessID()).thenReturn(42);
383 when(currentProcess.getAffinityMask()).thenReturn(3L);
384 when(currentProcess.getArguments()).thenReturn(List.of("--debug"));
385 when(currentProcess.getEnvironmentVariables()).thenReturn(java.util.Map.of("ENV", "value"));
386
387 when(operatingSystem.getProcessCount()).thenReturn(10);
388 when(operatingSystem.getThreadCount()).thenReturn(20);
389 when(operatingSystem.getProcesses(OperatingSystem.ProcessFiltering.ALL_PROCESSES,
390 OperatingSystem.ProcessSorting.CPU_DESC, 5)).thenReturn(List.of(topProcess));
391
392 when(topProcess.getProcessID()).thenReturn(7);
393 when(topProcess.getKernelTime()).thenReturn(30L);
394 when(topProcess.getUserTime()).thenReturn(20L);
395 when(topProcess.getUpTime()).thenReturn(100L);
396 when(topProcess.getResidentMemory()).thenReturn(512L);
397 when(topProcess.getVirtualSize()).thenReturn(2048L);
398 when(topProcess.getName()).thenReturn("java");
399 when(memory.getTotal()).thenReturn(4096L);
400
401 invokeStatic("printProcesses", OperatingSystem.class, operatingSystem, GlobalMemory.class,
402 memory);
403
404 String rendered = String.join("\n", getOshiData());
405 assertTrue(rendered.contains("My PID: 42 with affinity 11"));
406 assertTrue(rendered.contains("Processes: 10, Threads: 20"));
407 assertTrue(rendered.contains("java"));
408 assertTrue(rendered.contains("Current process arguments:"));
409 assertTrue(rendered.contains("--debug"));
410 assertTrue(rendered.contains("ENV=value"));
411 }
412
413 private void invokeStatic(String methodName, Class<?> firstType, Object firstArg,
414 Class<?> secondType, Object secondArg) throws Exception {
415 Method method = OshiController.class.getDeclaredMethod(methodName, firstType, secondType);
416 method.setAccessible(true);
417 method.invoke(null, firstArg, secondArg);
418 }
419 }