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.plugin.surefire;
20  
21  import java.io.File;
22  import java.lang.reflect.Method;
23  import java.nio.file.Path;
24  import java.nio.file.Paths;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.commons.io.FilenameUtils;
30  import org.apache.maven.execution.MavenSession;
31  import org.apache.maven.plugin.MojoFailureException;
32  import org.apache.maven.toolchain.Toolchain;
33  import org.apache.maven.toolchain.ToolchainManager;
34  import org.apache.maven.toolchain.java.DefaultJavaToolChain;
35  import org.junit.jupiter.api.Disabled;
36  import org.junit.jupiter.api.Test;
37  import org.mockito.ArgumentCaptor;
38  import org.slf4j.Logger;
39  
40  import static java.io.File.separatorChar;
41  import static java.util.Collections.singletonList;
42  import static java.util.Collections.singletonMap;
43  import static org.apache.maven.surefire.booter.SystemUtils.toJdkHomeFromJre;
44  import static org.assertj.core.api.Assertions.assertThat;
45  import static org.junit.jupiter.api.Assertions.assertThrows;
46  import static org.mockito.Mockito.mock;
47  import static org.mockito.Mockito.times;
48  import static org.mockito.Mockito.verify;
49  import static org.mockito.Mockito.when;
50  
51  /**
52   * Test for {@link AbstractSurefireMojo}. jdkToolchain parameter
53   */
54  public class AbstractSurefireMojoToolchainsTest {
55  
56      /**
57       * Ensure that we use the toolchain found by getToolchain()
58       * when the jdkToolchain parameter is set.
59       */
60      @Test
61      public void shouldCallMethodWhenSpecSet() throws Exception {
62          AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
63          Toolchain expectedMethod = mock(Toolchain.class);
64          MockToolchainManager toolchainManager = new MockToolchainManager(expectedMethod, null);
65          mojo.setToolchainManager(toolchainManager);
66          mojo.setJdkToolchain(singletonMap("version", "1.8"));
67          Toolchain actual = invokeMethod(mojo, "getToolchain");
68          assertThat(actual).isSameAs(expectedMethod);
69      }
70  
71      /**
72       * Ensure that we use the toolchain from build context when
73       * no jdkToolchain map is configured in mojo parameters.
74       * getToolchain() returns the main maven toolchain from the build context
75       */
76      @Test
77      public void shouldFallthroughToBuildContextWhenNoSpecSet() throws Exception {
78          AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
79          Toolchain expectedFromContext = mock(Toolchain.class);
80          Toolchain expectedFromSpec = mock(Toolchain.class); // ensure it still behaves correctly even if not null
81          mojo.setToolchainManager(new MockToolchainManager(expectedFromSpec, expectedFromContext));
82          Toolchain actual = invokeMethod(mojo, "getToolchain");
83          assertThat(actual).isSameAs(expectedFromContext);
84      }
85  
86      // TODO Is this still required?
87      @Test
88      @Disabled
89      public void shouldThrowToolchain() throws Exception {
90          assertThrows(MojoFailureException.class, () -> invokeMethod(AbstractSurefireMojo.class, "getToolchain"));
91      }
92  
93      // TODO Is this still required?
94      @Test
95      @Disabled
96      public void shouldGetToolchain() throws Exception {
97          Toolchain expected = mock(Toolchain.class);
98          Toolchain actual = invokeMethod(AbstractSurefireMojo.class, "getToolchain");
99  
100         assertThat(actual).isSameAs(expected);
101     }
102 
103     /**
104      * Ensures that the environmentVariables map for launching a test jvm
105      * contains a Toolchain-driven entry when toolchain is set.
106      */
107     @Test
108     public void shouldChangeJavaHomeFromToolchain() throws Exception {
109         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
110         DefaultJavaToolChain toolchain = mock(DefaultJavaToolChain.class);
111         when(toolchain.findTool("java")).thenReturn("/path/from/toolchain");
112         when(toolchain.getJavaHome()).thenReturn("/some/path");
113         mojo.setToolchain(toolchain);
114 
115         assertThat(mojo.getEnvironmentVariables()).isEmpty();
116         JdkAttributes effectiveJvm = invokeMethod(mojo, "getEffectiveJvm");
117         assertThat(mojo.getEnvironmentVariables()).containsEntry("JAVA_HOME", "/some/path");
118         assertThat(effectiveJvm.getJvmExecutable().getPath())
119                 .contains("/path/from/toolchain".replace('/', separatorChar));
120     }
121 
122     @Test
123     public void shouldNotChangeJavaHomeFromToolchainIfAlreadySet() throws Exception {
124         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
125         mojo.setEnvironmentVariables(singletonMap("JAVA_HOME", "/already/set/path"));
126 
127         DefaultJavaToolChain toolchain = mock(DefaultJavaToolChain.class);
128         when(toolchain.findTool("java")).thenReturn("/path/from/toolchain");
129         when(toolchain.getJavaHome()).thenReturn("/some/path");
130         mojo.setToolchain(toolchain);
131 
132         JdkAttributes effectiveJvm = invokeMethod(mojo, "getEffectiveJvm");
133         assertThat(mojo.getEnvironmentVariables()).containsEntry("JAVA_HOME", "/already/set/path");
134         assertThat(effectiveJvm.getJvmExecutable().getPath())
135                 .contains("/path/from/toolchain".replace('/', separatorChar));
136     }
137 
138     /**
139      * Ensures that the environmentVariables map for launching a test jvm
140      * contains a jvm parameter-driven entry when jvm is set.
141      */
142     @Test
143     public void shouldChangeJavaHomeFromJvm() throws Exception {
144         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
145 
146         File currentJdkHome = toJdkHomeFromJre();
147         String javaExecutablePath = FilenameUtils.concat(currentJdkHome.getAbsolutePath(), "bin/java");
148 
149         mojo.setJvm(javaExecutablePath);
150 
151         assertThat(mojo.getEnvironmentVariables()).isEmpty();
152         JdkAttributes effectiveJvm = invokeMethod(mojo, "getEffectiveJvm");
153         assertThat(mojo.getEnvironmentVariables()).containsEntry("JAVA_HOME", currentJdkHome.getAbsolutePath());
154         assertThat(effectiveJvm.getJvmExecutable().getPath()).contains(javaExecutablePath);
155     }
156 
157     /**
158      * Ensures that users can manually configure a value for JAVA_HOME
159      * and we will not override it
160      */
161     @Test
162     public void shouldNotChangeJavaHomeFromJvmIfAlreadySet() throws Exception {
163         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
164         mojo.setEnvironmentVariables(singletonMap("JAVA_HOME", "/already/set/path"));
165 
166         File currentJdkHome = toJdkHomeFromJre();
167         String javaExecutablePath = FilenameUtils.concat(currentJdkHome.getAbsolutePath(), "bin/java");
168 
169         mojo.setJvm(javaExecutablePath);
170 
171         JdkAttributes effectiveJvm = invokeMethod(mojo, "getEffectiveJvm");
172         assertThat(mojo.getEnvironmentVariables()).containsEntry("JAVA_HOME", "/already/set/path");
173         assertThat(effectiveJvm.getJvmExecutable().getPath()).contains(javaExecutablePath);
174     }
175 
176     @Test
177     public void withoutJvmAndToolchain() throws Exception {
178         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
179         Logger logger = mock(Logger.class);
180         mojo.setLogger(logger);
181         ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
182         JdkAttributes effectiveJvm = invokeMethod(mojo, "getEffectiveJvm");
183 
184         assertThat(mojo.getJvm()).isNull();
185 
186         assertThat(mojo.getEnvironmentVariables()).isEmpty();
187 
188         assertThat(effectiveJvm).isNotNull();
189 
190         assertThat(effectiveJvm.getJvmExecutable()).isNotNull();
191 
192         Path javaHome = Paths.get(System.getProperty("java.home")).normalize();
193         boolean isLocalJvm =
194                 effectiveJvm.getJvmExecutable().toPath().normalize().startsWith(javaHome);
195         assertThat(isLocalJvm).isTrue();
196 
197         verify(logger, times(1)).debug(argument.capture());
198 
199         assertThat(argument.getValue()).startsWith("Using JVM: " + System.getProperty("java.home"));
200     }
201 
202     @Test
203     public void shouldFailWithWrongJvmExecPath() throws Exception {
204         AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
205         mojo.setLogger(mock(Logger.class));
206         mojo.setJvm(System.getProperty("user.dir"));
207 
208         MojoFailureException ex = assertThrows(MojoFailureException.class, () -> invokeMethod(mojo, "getEffectiveJvm"));
209         assertThat(ex.getMessage()).startsWith("Given path does not end with java executor");
210     }
211 
212     @SuppressWarnings("unchecked")
213     private static <T> T invokeMethod(Object target, String methodName, Object... args) throws Exception {
214         Class<?> clazz = target instanceof Class ? (Class<?>) target : target.getClass();
215         while (clazz != null) {
216             for (Method method : clazz.getDeclaredMethods()) {
217                 if (method.getName().equals(methodName) && method.getParameterCount() == args.length) {
218                     method.setAccessible(true);
219                     try {
220                         return (T) method.invoke(target instanceof Class ? null : target, args);
221                     } catch (java.lang.reflect.InvocationTargetException e) {
222                         Throwable cause = e.getCause();
223                         if (cause instanceof Exception) {
224                             throw (Exception) cause;
225                         }
226                         throw e;
227                     }
228                 }
229             }
230             clazz = clazz.getSuperclass();
231         }
232         throw new NoSuchMethodException(methodName);
233     }
234 
235     /**
236      * Mocks a ToolchainManager
237      */
238     public static final class MockToolchainManager implements ToolchainManager {
239 
240         private final Toolchain specToolchain;
241         private final Toolchain buildContextToolchain;
242 
243         public MockToolchainManager(Toolchain specToolchain, Toolchain buildContextToolchain) {
244             this.specToolchain = specToolchain;
245             this.buildContextToolchain = buildContextToolchain;
246         }
247 
248         @Override
249         public Toolchain getToolchainFromBuildContext(String type, MavenSession context) {
250             return buildContextToolchain;
251         }
252 
253         @Override
254         public List<Toolchain> getToolchains(MavenSession session, String type, Map<String, String> requirements) {
255             return specToolchain == null ? Collections.<Toolchain>emptyList() : singletonList(specToolchain);
256         }
257     }
258 }