View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
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   * Tests for {@link OshiController}.
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 }