View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os;
6   
7   import static org.hamcrest.MatcherAssert.assertThat;
8   import static org.hamcrest.Matchers.anything;
9   import static org.hamcrest.Matchers.both;
10  import static org.hamcrest.Matchers.closeTo;
11  import static org.hamcrest.Matchers.empty;
12  import static org.hamcrest.Matchers.emptyString;
13  import static org.hamcrest.Matchers.greaterThan;
14  import static org.hamcrest.Matchers.greaterThanOrEqualTo;
15  import static org.hamcrest.Matchers.hasSize;
16  import static org.hamcrest.Matchers.is;
17  import static org.hamcrest.Matchers.lessThan;
18  import static org.hamcrest.Matchers.lessThanOrEqualTo;
19  import static org.hamcrest.Matchers.not;
20  import static org.hamcrest.Matchers.notNullValue;
21  import static org.hamcrest.Matchers.oneOf;
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.junit.jupiter.api.BeforeAll;
33  import org.junit.jupiter.api.Test;
34  import org.junit.jupiter.api.TestInstance;
35  import org.junit.jupiter.api.TestInstance.Lifecycle;
36  
37  import com.sun.jna.Platform;
38  
39  import oshi.SystemInfo;
40  import oshi.software.os.OSProcess.State;
41  import oshi.software.os.OperatingSystem.OSVersionInfo;
42  import oshi.software.os.OperatingSystem.ProcessFiltering;
43  import oshi.software.os.OperatingSystem.ProcessSorting;
44  
45  /**
46   * Test OS
47   */
48  @TestInstance(Lifecycle.PER_CLASS)
49  class OperatingSystemTest {
50  
51      private OperatingSystem os = new SystemInfo().getOperatingSystem();
52      private OSProcess proc = os.getProcess(os.getProcessId());
53  
54      @BeforeAll
55      void setUp() {
56          // In rare cases on procfs based systems the proc call may result in null, so
57          // we'll try a second time
58          if (this.proc == null) {
59              this.proc = os.getProcess(os.getProcessId());
60          }
61          // Fail here rather than more confusing NPEs later
62          assertThat("Current process PID returned null", proc, is(notNullValue()));
63      }
64  
65      @Test
66      void testOperatingSystem() {
67          assertThat("OS family shouldn't be null", os.getFamily(), is(notNullValue()));
68          assertThat("OS manufacturer shouldn't be null", os.getManufacturer(), is(notNullValue()));
69          OSVersionInfo versionInfo = os.getVersionInfo();
70          assertThat("OS version info shouldn't be null", versionInfo, is(notNullValue()));
71  
72          assertThat("OS uptime in seconds should be greater than 0", os.getSystemUptime(), is(greaterThan(0L)));
73          assertThat("OS boot time in seconds since Unix epoch should be greater than 0", os.getSystemBootTime(),
74                  is(greaterThan(0L)));
75          assertThat("OS boot time in seconds since Unix epoch should be before the current time", os.getSystemBootTime(),
76                  is(lessThan(System.currentTimeMillis() / 1000L)));
77  
78          assertThat("OS should have 1 or more currently running processes", os.getProcessCount(), is(greaterThan(0)));
79          assertThat("OS should have 1 or more currently running threads", os.getThreadCount(), is(greaterThan(0)));
80          assertThat("OS bitness should either be 32 or 64 ", os.getBitness(), is(oneOf(32, 64)));
81          assertThat("The current process id should be greater than 0", os.getProcessId(), is(greaterThan(0)));
82          // Just exercise this code without error
83          assertThat(
84                  "The current process' permissions (if has sudo or Administrator privileges) should be determined correctly",
85                  os.isElevated(), is(anything()));
86  
87          assertThat("OS should have at least 1 currently running process", os.getProcesses(null, null, 0),
88                  is(not(empty())));
89      }
90  
91      @Test
92      void testProcessStrings() {
93          assertThat("Current running process name shouldn't be empty", proc.getName(), is(not(emptyString())));
94          assertThat("Current running process path name shouldn't be empty", proc.getPath(), is(not(emptyString())));
95          assertThat("Current running process command line shouldn't be null", proc.getCommandLine(), is(notNullValue()));
96          assertThat("Current running process working directory shouldn't be null", proc.getCurrentWorkingDirectory(),
97                  is(notNullValue()));
98          assertThat("Current running process user name shouldn't be null", proc.getUser(), is(notNullValue()));
99          assertThat("Current running process user id shouldn't be null ", proc.getUserID(), is(notNullValue()));
100         assertThat("Current running process group shouldn't be null", proc.getGroup(), is(notNullValue()));
101         assertThat("Current running process group id shouldn't be null", proc.getGroupID(), is(notNullValue()));
102     }
103 
104     @Test
105     void testProcessStats() {
106         assertThat("Current running process state shouldn't be INVALID", proc.getState(), is(not(State.INVALID)));
107         assertThat("Current running process id should be equal to the OS current running process id", os.getProcessId(),
108                 is(proc.getProcessID()));
109         assertThat("Current running process parent process id should be 0 or higher", proc.getParentProcessID(),
110                 is(greaterThanOrEqualTo(0)));
111         assertThat("Current running process thread count should be greater than 0", proc.getThreadCount(),
112                 is(greaterThan(0)));
113         if (Platform.isAIX()) {
114             assertThat("Current running process priority should be between -20 and 255", proc.getPriority(),
115                     is(both(greaterThanOrEqualTo(1)).and(lessThanOrEqualTo(255))));
116         } else {
117             assertThat("Current running process priority should be between -20 and 128", proc.getPriority(),
118                     is(both(greaterThanOrEqualTo(-20)).and(lessThanOrEqualTo(128))));
119         }
120         assertThat("Current running process virtual memory size should be 0 or higher", proc.getVirtualSize(),
121                 is(greaterThanOrEqualTo(0L)));
122         assertThat("Current running process resident set size should be 0 or higher", proc.getResidentSetSize(),
123                 is(greaterThanOrEqualTo(0L)));
124         assertThat("Current running process time elapsed in system/kernel should be 0 or higher", proc.getKernelTime(),
125                 is(greaterThanOrEqualTo(0L)));
126         assertThat("Current running process time elapsed in user mode should be 0 or higher", proc.getUserTime(),
127                 is(greaterThanOrEqualTo(0L)));
128         assertThat("Current running process uptime should be 0 or higher", proc.getUpTime(),
129                 is(greaterThanOrEqualTo(0L)));
130         assertThat("Current process minor faults should be 0 or higher", proc.getMinorFaults(),
131                 is(greaterThanOrEqualTo(0L)));
132         assertThat("Current process major faults should be 0 or higher", proc.getMajorFaults(),
133                 is(greaterThanOrEqualTo(0L)));
134         assertThat("Current process context switches should be 0 or higher", proc.getContextSwitches(),
135                 is(greaterThanOrEqualTo(0L)));
136         assertThat("Current process cumulative cpu usage should be 0.0 or higher", proc.getProcessCpuLoadCumulative(),
137                 is(greaterThanOrEqualTo(0d)));
138         assertThat("Current process cumulative cpu usage should be the same as the current process",
139                 proc.getProcessCpuLoadBetweenTicks(null),
140                 is(closeTo(proc.getProcessCpuLoadCumulative(), Double.MIN_VALUE)));
141         assertThat(
142                 "Current process cumulative cpu usage should be the same for a previous snapshot of the same process",
143                 proc.getProcessCpuLoadBetweenTicks(proc),
144                 is(closeTo(proc.getProcessCpuLoadCumulative(), Double.MIN_VALUE)));
145         assertThat("Current process start time should be 0 or higher", proc.getStartTime(),
146                 is(greaterThanOrEqualTo(0L)));
147         assertThat("Current process bytes read from disk should be 0 or higher", proc.getBytesRead(),
148                 is(greaterThanOrEqualTo(0L)));
149         assertThat("Current process bytes written to disk should be 0 or higher", proc.getBytesWritten(),
150                 is(greaterThanOrEqualTo(0L)));
151         assertThat("Process bitness can't exceed OS bitness", proc.getBitness(),
152                 is(lessThanOrEqualTo(os.getBitness())));
153         assertThat("Bitness must be 0, 32 or 64", proc.getBitness(), is(oneOf(0, 32, 64)));
154         assertThat("Current process open file handles should be -1 or higher", proc.getOpenFiles(),
155                 is(greaterThanOrEqualTo(0L)));
156         assertThat("Soft open file limit for process should be -1 or higher", proc.getSoftOpenFileLimit(),
157                 is(greaterThanOrEqualTo(-1L)));
158         assertThat("Hard open file limit for process should be -1 or higher", proc.getHardOpenFileLimit(),
159                 is(greaterThanOrEqualTo(-1L)));
160     }
161 
162     @Test
163     void testThreads() {
164         List<OSThread> threads = proc.getThreadDetails();
165         for (OSThread thread : threads) {
166             assertThat("OS thread shouldn't be null", thread, is(notNullValue()));
167         }
168     }
169 
170     /**
171      * Tests process query by pid list
172      */
173     @Test
174     void testProcessQueryByList() {
175         SystemInfo si = new SystemInfo();
176         OperatingSystem os = si.getOperatingSystem();
177         assertThat("OS family shouldn't be null", os.getFamily(), is(notNullValue()));
178         assertThat("OS manufacturer shouldn't be null", os.getManufacturer(), is(notNullValue()));
179         OSVersionInfo versionInfo = os.getVersionInfo();
180         assertThat("OS version info shouldn't be null", versionInfo, is(notNullValue()));
181 
182         assertThat("OS currently running processes should be 1 or higher", os.getProcessCount(), is(greaterThan(0)));
183         assertThat("OS thread count should be 1 or higher", os.getThreadCount(), is(greaterThan(0)));
184         assertThat("OS current running process id should be 0 or higher", os.getProcessId(),
185                 is(greaterThanOrEqualTo(0)));
186 
187         List<OSProcess> processes = os.getProcesses(null, null, 0);
188         assertThat("Currently running processes shouldn't be null", processes, is(notNullValue()));
189         assertThat("every OS should have at least one process running on it", processes, is(not(empty())));
190         // the list of pids we want info on
191         List<Integer> pids = new ArrayList<>();
192         for (OSProcess p : processes) {
193             pids.add(p.getProcessID());
194         }
195         // query for just those processes
196         Collection<OSProcess> processes1 = os.getProcesses(pids);
197         // there's a potential for a race condition here, if a process we
198         // queried for initially wasn't running during the second query. In this case,
199         // try again with the shorter list
200         while (processes1.size() < pids.size()) {
201             pids.clear();
202             for (OSProcess p : processes1) {
203                 pids.add(p.getProcessID());
204             }
205             // query for just those processes
206             processes1 = os.getProcesses(pids);
207         }
208         assertThat("OS processes should match processes with pids we want info on", pids, hasSize(processes1.size()));
209 
210     }
211 
212     /**
213      * Tests child and dependent process getter
214      */
215     @Test
216     void testGetChildAndDependentProcesses() {
217         // Testing child processes is tricky because we don't really know a priori what
218         // processes might have children, and if we do test the full list vs. individual
219         // processes, we run into a race condition where child processes can start or
220         // stop before we measure a second time. So we can't really test for one-to-one
221         // correspondence of child process lists.
222         //
223         // We can expect code logic failures to occur all/most of the time for
224         // categories of processes, however, and allow occasional differences due to
225         // race conditions. So we will test three categories of processes: Those with 0
226         // children, those with exactly 1 child process, and those with multiple child
227         // processes. On the second poll, we expect at least half of processes in those
228         // categories to still be in the same category.
229         //
230         SystemInfo si = new SystemInfo();
231         OperatingSystem os = si.getOperatingSystem();
232         List<OSProcess> processes = os.getProcesses(null, null, 0);
233         Map<Integer, Long> zeroChildMap = new HashMap<>();
234         Map<Integer, Long> oneChildMap = new HashMap<>();
235         Map<Integer, Long> manyChildMap = new HashMap<>();
236         // Initialize all processes with no children
237         for (OSProcess p : processes) {
238             zeroChildMap.put(p.getProcessID(), p.getStartTime());
239         }
240         // Move parents with 1 or more children to other set
241         for (OSProcess p : processes) {
242             int ppid = p.getParentProcessID();
243             long startTime = p.getStartTime();
244             if (zeroChildMap.containsKey(ppid) && zeroChildMap.get(ppid) >= startTime) {
245                 // Zero to One
246                 oneChildMap.put(ppid, zeroChildMap.get(ppid));
247                 zeroChildMap.remove(ppid);
248             } else if (oneChildMap.containsKey(ppid) && oneChildMap.get(ppid) >= startTime) {
249                 // One to many
250                 manyChildMap.put(ppid, oneChildMap.get(ppid));
251                 oneChildMap.remove(ppid);
252             }
253         }
254         // Now test that majority of each set is in same category
255         // Zero
256         int matchedChild = 0;
257         int matchedDescendant = 0;
258         int descendantNotLessThanChild = 0;
259         if (zeroChildMap.size() > 9) {
260             int total = 0;
261             for (Integer i : zeroChildMap.keySet()) {
262                 List<OSProcess> children = os.getChildProcesses(i, null, null, 0);
263                 List<OSProcess> descendants = os.getDescendantProcesses(i, null, null, 0);
264                 if (children.size() == 0) {
265                     matchedChild++;
266                 }
267                 if (descendants.size() == 0) {
268                     matchedDescendant++;
269                 }
270                 // This is more than enough to test
271                 if (++total > 9) {
272                     break;
273                 }
274             }
275             assertThat("Most processes with no children should not suddenly have them.", matchedChild,
276                     is(greaterThan(total / 3)));
277             assertThat("Most processes with no children should not suddenly have descendants.", matchedDescendant,
278                     is(greaterThan(total / 3)));
279         }
280         // One child
281         matchedChild = 0;
282         matchedDescendant = 0;
283         descendantNotLessThanChild = 0;
284         if (oneChildMap.size() > 9) {
285             int total = 0;
286             for (Integer i : oneChildMap.keySet()) {
287                 List<OSProcess> children = os.getChildProcesses(i, null, null, 0);
288                 List<OSProcess> descendants = os.getDescendantProcesses(i, null, null, 0);
289                 if (children.size() == 1) {
290                     matchedChild++;
291                 }
292                 if (descendants.size() >= 1) {
293                     matchedDescendant++;
294                 }
295                 if (descendants.size() >= children.size()) {
296                     descendantNotLessThanChild++;
297                 }
298                 // This is more than enough to test
299                 if (++total > 9) {
300                     break;
301                 }
302             }
303             assertThat("Most processes with one child should not suddenly have zero or more than one.", matchedChild,
304                     is(greaterThan(total / 3)));
305             assertThat("Most processes with one child should not suddenly have zero descendants.", matchedDescendant,
306                     is(greaterThan(total / 3)));
307             assertThat("Most processes with one child should have no more children than descendants",
308                     descendantNotLessThanChild, is(greaterThan(total / 3)));
309         }
310         // Many children
311         matchedChild = 0;
312         matchedDescendant = 0;
313         descendantNotLessThanChild = 0;
314         if (manyChildMap.size() > 9) {
315             int total = 0;
316             for (Integer i : manyChildMap.keySet()) {
317                 // Use a non-null sorting for test purposes
318                 List<OSProcess> children = os.getChildProcesses(i, ProcessFiltering.VALID_PROCESS,
319                         ProcessSorting.CPU_DESC, Integer.MAX_VALUE);
320                 List<OSProcess> descendants = os.getDescendantProcesses(i, ProcessFiltering.VALID_PROCESS,
321                         ProcessSorting.CPU_DESC, Integer.MAX_VALUE);
322                 if (children.size() > 0) {
323                     matchedChild++;
324                 }
325                 if (descendants.size() > 0) {
326                     matchedDescendant++;
327                 }
328                 if (descendants.size() >= children.size()) {
329                     descendantNotLessThanChild++;
330                 }
331                 // This is more than enough to test
332                 if (++total > 9) {
333                     break;
334                 }
335             }
336             assertThat("Most processes with more than one child should not suddenly have none.", matchedChild,
337                     is(greaterThan(total / 3)));
338             assertThat("Most processes with more than one child should not suddenly have no descendants.",
339                     matchedDescendant, is(greaterThan(total / 3)));
340             assertThat("Most processes with more than one child should have no more children than descendants",
341                     descendantNotLessThanChild, is(greaterThan(total / 3)));
342         }
343     }
344 
345     @Test
346     void testGetCommandLine() {
347         int processesWithNonEmptyCmdLine = 0;
348 
349         SystemInfo si = new SystemInfo();
350         OperatingSystem os = si.getOperatingSystem();
351         for (OSProcess process : os.getProcesses(null, null, 0)) {
352             if (!process.getCommandLine().trim().isEmpty()) {
353                 processesWithNonEmptyCmdLine++;
354             }
355         }
356 
357         assertThat("Processes with non-empty command line should be 1 or higher", processesWithNonEmptyCmdLine,
358                 is(greaterThan(0)));
359     }
360 
361     @Test
362     void testGetArguments() {
363         SystemInfo si = new SystemInfo();
364         OperatingSystem os = si.getOperatingSystem();
365         List<OSProcess> processesWithNonEmptyArguments = os.getProcesses(p -> !p.getArguments().isEmpty(), null, 0);
366 
367         assertThat("Processes with non-empty arguments should be non-empty", processesWithNonEmptyArguments,
368                 not(empty()));
369     }
370 
371     @Test
372     void testGetEnvironment() {
373         int processesWithNonEmptyEnvironment = 0;
374 
375         SystemInfo si = new SystemInfo();
376         OperatingSystem os = si.getOperatingSystem();
377         for (OSProcess process : os.getProcesses(null, null, 0)) {
378             if (!process.getEnvironmentVariables().isEmpty()) {
379                 processesWithNonEmptyEnvironment++;
380             }
381         }
382 
383         assertThat("Processes with non-empty environment should be 1 or higher", processesWithNonEmptyEnvironment,
384                 is(greaterThan(0)));
385     }
386 
387     /**
388      * Tests services getter
389      */
390     @Test
391     void testGetServices() {
392         SystemInfo si = new SystemInfo();
393         OperatingSystem os = si.getOperatingSystem();
394         List<OSService> services = os.getServices();
395         // macOS CI typically has none, though, and some linux distros aren't covered
396         if (!services.isEmpty()) {
397             int stopped = 0;
398             int running = 0;
399             for (OSService svc : services) {
400                 assertThat(svc.getName(), is(not(emptyString())));
401                 switch (svc.getState()) {
402                 case STOPPED:
403                     stopped++;
404                     break;
405                 case RUNNING:
406                     running++;
407                     break;
408                 default:
409                     break;
410                 }
411             }
412             // Should be at least one of each
413             assertThat("There should be at least 1 stopped service", stopped, is(greaterThan(0)));
414             assertThat("There should be at least 1 running service", running, is(greaterThan(0)));
415         }
416     }
417 
418     /**
419      * Tests sessions getter
420      */
421     @Test
422     void testGetSessions() {
423         SystemInfo si = new SystemInfo();
424         OperatingSystem os = si.getOperatingSystem();
425         for (OSSession sess : os.getSessions()) {
426             assertThat("Logged in user's name for the session shouldn't be empty", sess.getUserName(),
427                     is(not(emptyString())));
428             assertThat("Sessions' terminal device name shouldn't be empty", sess.getTerminalDevice(),
429                     is(not(emptyString())));
430             // Login time
431             assertThat(
432                     String.format(Locale.ROOT, "Logon time should be before now: %d < %d%n%s", sess.getLoginTime(),
433                             System.currentTimeMillis(), sess),
434                     sess.getLoginTime(), is(lessThanOrEqualTo(System.currentTimeMillis())));
435             assertThat("Session host shouldn't be null", sess.getHost(), is(notNullValue()));
436         }
437     }
438 
439     /**
440      * Test get desktop windows
441      */
442     @Test
443     void testGetDesktopWindows() {
444         SystemInfo si = new SystemInfo();
445         OperatingSystem os = si.getOperatingSystem();
446         List<OSDesktopWindow> allWindows = os.getDesktopWindows(false);
447         List<OSDesktopWindow> visibleWindows = os.getDesktopWindows(true);
448         assertThat("Visible should be a subset of all windows", visibleWindows.size(),
449                 is(lessThanOrEqualTo(allWindows.size())));
450         Set<Long> windowIds = new HashSet<>();
451         for (OSDesktopWindow dw : visibleWindows) {
452             assertThat("Visible window should be visible", dw.isVisible(), is(true));
453             assertThat("Height should be nononegative", dw.getLocAndSize().height, is(greaterThanOrEqualTo(0)));
454             assertThat("Width should be nononegative", dw.getLocAndSize().width, is(greaterThanOrEqualTo(0)));
455             windowIds.add(dw.getWindowId());
456         }
457         assertThat("Window IDs should be unique", windowIds.size(), is(visibleWindows.size()));
458     }
459 }