View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.booter;
20  
21  import java.lang.management.ManagementFactory;
22  
23  import org.junit.Assume;
24  import org.junit.Test;
25  
26  import static org.assertj.core.api.Assertions.assertThat;
27  import static org.assertj.core.api.Assertions.assertThatThrownBy;
28  import static org.junit.Assume.assumeTrue;
29  
30  /**
31   * Tests for {@link ProcessHandleChecker}.
32   * <p>
33   * These tests use reflection-based PID detection to work on both Java 8 and Java 9+.
34   */
35  public class ProcessHandleCheckerTest {
36  
37      @Test
38      public void shouldReportAvailableOnJava9Plus() {
39          // This test runs on modern JVMs, so isAvailable() should return true
40          // FIXME DisabledOnJre when we migrate to junit5
41          double v = Double.parseDouble(System.getProperty("java.specification.version"));
42          Assume.assumeTrue(v >= 9.0);
43          assertThat(ProcessHandleChecker.isAvailable()).isTrue();
44      }
45  
46      @Test
47      public void shouldDetectCurrentProcessAsAlive() {
48          assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
49  
50          String currentPid = getCurrentPid();
51          assumeTrue("Could not determine current PID", currentPid != null);
52  
53          ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
54  
55          assertThat(checker.canUse()).isTrue();
56          assertThat(checker.isProcessAlive()).isTrue();
57          assertThat(checker.isStopped()).isFalse();
58      }
59  
60      @Test
61      public void shouldDetectNonExistentProcessAsNotUsable() {
62          assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
63  
64          // Use an invalid PID that's unlikely to exist
65          ProcessHandleChecker checker = new ProcessHandleChecker("999999999");
66  
67          assertThat(checker.canUse()).isTrue();
68      }
69  
70      @Test
71      public void shouldStopChecker() {
72          assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
73  
74          String currentPid = getCurrentPid();
75          assumeTrue("Could not determine current PID", currentPid != null);
76  
77          ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
78  
79          assertThat(checker.canUse()).isTrue();
80          assertThat(checker.isStopped()).isFalse();
81  
82          checker.stop();
83  
84          assertThat(checker.isStopped()).isTrue();
85          assertThat(checker.canUse()).isFalse();
86      }
87  
88      @Test
89      public void shouldDestroyActiveCommands() {
90          assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
91  
92          String currentPid = getCurrentPid();
93          assumeTrue("Could not determine current PID", currentPid != null);
94  
95          ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
96  
97          assertThat(checker.canUse()).isTrue();
98  
99          checker.destroyActiveCommands();
100 
101         assertThat(checker.isStopped()).isTrue();
102         assertThat(checker.canUse()).isFalse();
103     }
104 
105     @Test
106     public void shouldReturnMeaningfulToString() {
107         assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
108 
109         String currentPid = getCurrentPid();
110         assumeTrue("Could not determine current PID", currentPid != null);
111 
112         ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
113 
114         String toString = checker.toString();
115 
116         assertThat(toString)
117                 .contains("ProcessHandleChecker")
118                 .contains("pid=" + currentPid)
119                 .contains("stopped=false");
120     }
121 
122     @Test
123     public void shouldReturnToStringWithStartInstantAfterCanUse() {
124         assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
125 
126         String currentPid = getCurrentPid();
127         assumeTrue("Could not determine current PID", currentPid != null);
128 
129         ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
130 
131         checker.canUse();
132         String toString = checker.toString();
133 
134         assertThat(toString).contains("ProcessHandleChecker").contains("hasHandle=true");
135     }
136 
137     @Test
138     public void shouldCreateViaFactoryMethod() {
139         assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
140 
141         String currentPid = getCurrentPid();
142         assumeTrue("Could not determine current PID", currentPid != null);
143 
144         ProcessChecker checker = ProcessChecker.of(currentPid);
145 
146         assertThat(checker).isInstanceOf(ProcessHandleChecker.class);
147         assertThat(checker.canUse()).isTrue();
148         assertThat(checker.isProcessAlive()).isTrue();
149     }
150 
151     @Test
152     public void shouldReturnNullFromFactoryForNullPpid() {
153         ProcessChecker checker = ProcessChecker.of(null);
154 
155         assertThat(checker).isNull();
156     }
157 
158     @Test
159     public void shouldThrowOnInvalidPpidFormat() {
160         assertThatThrownBy(() -> new ProcessHandleChecker("not-a-number")).isInstanceOf(NumberFormatException.class);
161     }
162 
163     @Test
164     public void shouldReturnProcessInfoAfterCanUse() {
165         assumeTrue("ProcessHandle not available", ProcessHandleChecker.isAvailable());
166 
167         String currentPid = getCurrentPid();
168         assumeTrue("Could not determine current PID", currentPid != null);
169 
170         ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
171 
172         // Now processInfo() should return valid info
173         ProcessInfo processInfo = checker.processInfo();
174         assertThat(processInfo).isNotNull();
175         assertThat(processInfo.getPID()).isEqualTo(currentPid);
176         assertThat(processInfo.getTime()).isGreaterThan(0L);
177     }
178 
179     /**
180      * Gets the current process PID using reflection (Java 8 compatible).
181      *
182      * @return the current process PID as a string, or null if it cannot be determined
183      */
184     private static String getCurrentPid() {
185         // Try ProcessHandle.current().pid() via reflection (Java 9+)
186         try {
187             Class<?> processHandleClass = Class.forName("java.lang.ProcessHandle");
188             Object currentHandle = processHandleClass.getMethod("current").invoke(null);
189             Long pid = (Long) processHandleClass.getMethod("pid").invoke(currentHandle);
190             return String.valueOf(pid);
191         } catch (Exception e) {
192             // Fall back to ManagementFactory (works on Java 8)
193             try {
194                 String name = ManagementFactory.getRuntimeMXBean().getName();
195                 int atIndex = name.indexOf('@');
196                 if (atIndex > 0) {
197                     return name.substring(0, atIndex);
198                 }
199             } catch (Exception ex) {
200                 // Ignore
201             }
202         }
203         return null;
204     }
205 }