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.cling.executor;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.Paths;
26  import java.util.Collection;
27  import java.util.List;
28  
29  import org.apache.maven.api.annotations.Nullable;
30  import org.apache.maven.api.cli.Executor;
31  import org.apache.maven.api.cli.ExecutorRequest;
32  import org.apache.maven.cling.executor.embedded.EmbeddedMavenExecutor;
33  import org.apache.maven.cling.executor.forked.ForkedMavenExecutor;
34  import org.junit.jupiter.api.AfterAll;
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.api.Timeout;
37  import org.junit.jupiter.api.condition.DisabledOnOs;
38  import org.junit.jupiter.api.io.CleanupMode;
39  import org.junit.jupiter.api.io.TempDir;
40  
41  import static org.junit.jupiter.api.Assertions.assertEquals;
42  import static org.junit.jupiter.api.Assertions.assertFalse;
43  import static org.junit.jupiter.api.Assertions.assertTrue;
44  import static org.junit.jupiter.api.condition.OS.WINDOWS;
45  
46  public abstract class MavenExecutorTestSupport {
47      @Timeout(15)
48      @Test
49      void mvnenc(
50              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd,
51              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome)
52              throws Exception {
53          String logfile = "m4.log";
54          execute(
55                  cwd.resolve(logfile),
56                  List.of(mvn4ExecutorRequestBuilder()
57                          .command("mvnenc")
58                          .cwd(cwd)
59                          .userHomeDirectory(userHome)
60                          .argument("diag")
61                          .argument("-l")
62                          .argument(logfile)
63                          .build()));
64          System.out.println(Files.readString(cwd.resolve(logfile)));
65      }
66  
67      @DisabledOnOs(
68              value = WINDOWS,
69              disabledReason = "JUnit on Windows fails to clean up as mvn3 does not close log file properly")
70      @Timeout(15)
71      @Test
72      void dump3(
73              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd,
74              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome)
75              throws Exception {
76          String logfile = "m3.log";
77          execute(
78                  cwd.resolve(logfile),
79                  List.of(mvn3ExecutorRequestBuilder()
80                          .cwd(cwd)
81                          .userHomeDirectory(userHome)
82                          .argument("eu.maveniverse.maven.plugins:toolbox:0.7.4:gav-dump")
83                          .argument("-l")
84                          .argument(logfile)
85                          .build()));
86          System.out.println(Files.readString(cwd.resolve(logfile)));
87      }
88  
89      @Timeout(15)
90      @Test
91      void dump4(
92              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd,
93              @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome)
94              throws Exception {
95          String logfile = "m4.log";
96          execute(
97                  cwd.resolve(logfile),
98                  List.of(mvn4ExecutorRequestBuilder()
99                          .cwd(cwd)
100                         .userHomeDirectory(userHome)
101                         .argument("eu.maveniverse.maven.plugins:toolbox:0.7.4:gav-dump")
102                         .argument("-l")
103                         .argument(logfile)
104                         .build()));
105         System.out.println(Files.readString(cwd.resolve(logfile)));
106     }
107 
108     @Timeout(15)
109     @Test
110     void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception {
111         layDownFiles(tempDir);
112         String logfile = "m4.log";
113         execute(
114                 tempDir.resolve(logfile),
115                 List.of(mvn4ExecutorRequestBuilder()
116                         .cwd(tempDir)
117                         .argument("-V")
118                         .argument("verify")
119                         .argument("-l")
120                         .argument(logfile)
121                         .build()));
122     }
123 
124     @Timeout(15)
125     @Test
126     void version() throws Exception {
127         assertEquals(
128                 System.getProperty("maven4version"),
129                 mavenVersion(mvn4ExecutorRequestBuilder().build()));
130     }
131 
132     @DisabledOnOs(
133             value = WINDOWS,
134             disabledReason = "JUnit on Windows fails to clean up as mvn3 does not close log file properly")
135     @Timeout(15)
136     @Test
137     void defaultFs3x(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception {
138         layDownFiles(tempDir);
139         String logfile = "m3.log";
140         execute(
141                 tempDir.resolve(logfile),
142                 List.of(mvn3ExecutorRequestBuilder()
143                         .cwd(tempDir)
144                         .argument("-V")
145                         .argument("verify")
146                         .argument("-l")
147                         .argument(logfile)
148                         .build()));
149     }
150 
151     @Timeout(15)
152     @Test
153     void version3x() throws Exception {
154         assertEquals(
155                 System.getProperty("maven3version"),
156                 mavenVersion(mvn3ExecutorRequestBuilder().build()));
157     }
158 
159     @Timeout(15)
160     @Test
161     void defaultFsCaptureOutput(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception {
162         layDownFiles(tempDir);
163         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
164         execute(
165                 null,
166                 List.of(mvn4ExecutorRequestBuilder()
167                         .cwd(tempDir)
168                         .argument("-V")
169                         .argument("verify")
170                         .stdOut(stdout)
171                         .build()));
172         assertFalse(stdout.toString().contains("[\u001B["), "By default no ANSI color codes");
173         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
174     }
175 
176     @Timeout(15)
177     @Test
178     void defaultFsCaptureOutputWithForcedColor(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir)
179             throws Exception {
180         layDownFiles(tempDir);
181         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
182         execute(
183                 null,
184                 List.of(mvn4ExecutorRequestBuilder()
185                         .cwd(tempDir)
186                         .argument("-V")
187                         .argument("verify")
188                         .argument("--color=yes")
189                         .stdOut(stdout)
190                         .build()));
191         assertTrue(stdout.toString().contains("[\u001B["), "No ANSI codes present");
192         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
193     }
194 
195     @Timeout(15)
196     @Test
197     void defaultFsCaptureOutputWithForcedOffColor(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir)
198             throws Exception {
199         layDownFiles(tempDir);
200         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
201         execute(
202                 null,
203                 List.of(mvn4ExecutorRequestBuilder()
204                         .cwd(tempDir)
205                         .argument("-V")
206                         .argument("verify")
207                         .argument("--color=no")
208                         .stdOut(stdout)
209                         .build()));
210         assertFalse(stdout.toString().contains("[\u001B["), "No ANSI codes present");
211         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
212     }
213 
214     @Timeout(15)
215     @Test
216     void defaultFs3xCaptureOutput(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception {
217         layDownFiles(tempDir);
218         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
219         execute(
220                 null,
221                 List.of(mvn3ExecutorRequestBuilder()
222                         .cwd(tempDir)
223                         .argument("-V")
224                         .argument("verify")
225                         .stdOut(stdout)
226                         .build()));
227         // Note: we do not validate ANSI as Maven3 is weird in this respect (thinks is color but is not)
228         // assertTrue(stdout.toString().contains("[\u001B["), "No ANSI codes present");
229         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
230     }
231 
232     @Timeout(15)
233     @Test
234     void defaultFs3xCaptureOutputWithForcedColor(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir)
235             throws Exception {
236         layDownFiles(tempDir);
237         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
238         execute(
239                 null,
240                 List.of(mvn3ExecutorRequestBuilder()
241                         .cwd(tempDir)
242                         .argument("-V")
243                         .argument("verify")
244                         .argument("--color=yes")
245                         .stdOut(stdout)
246                         .build()));
247         assertTrue(stdout.toString().contains("[\u001B["), "No ANSI codes present");
248         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
249     }
250 
251     @Timeout(15)
252     @Test
253     void defaultFs3xCaptureOutputWithForcedOffColor(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir)
254             throws Exception {
255         layDownFiles(tempDir);
256         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
257         execute(
258                 null,
259                 List.of(mvn3ExecutorRequestBuilder()
260                         .cwd(tempDir)
261                         .argument("-V")
262                         .argument("verify")
263                         .argument("--color=no")
264                         .stdOut(stdout)
265                         .build()));
266         assertFalse(stdout.toString().contains("[\u001B["), "No ANSI codes present");
267         assertTrue(stdout.toString().contains("INFO"), "No INFO found");
268     }
269 
270     public static final String POM_STRING =
271             """
272                 <?xml version="1.0" encoding="UTF-8"?>
273                 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
274                          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
275 
276                     <modelVersion>4.0.0</modelVersion>
277 
278                     <groupId>org.apache.maven.samples</groupId>
279                     <artifactId>sample</artifactId>
280                     <version>1.0.0</version>
281 
282                     <dependencyManagement>
283                       <dependencies>
284                         <dependency>
285                           <groupId>org.junit</groupId>
286                           <artifactId>junit-bom</artifactId>
287                           <version>5.11.1</version>
288                           <type>pom</type>
289                           <scope>import</scope>
290                         </dependency>
291                       </dependencies>
292                     </dependencyManagement>
293 
294                     <dependencies>
295                       <dependency>
296                         <groupId>org.junit.jupiter</groupId>
297                         <artifactId>junit-jupiter-api</artifactId>
298                         <scope>test</scope>
299                       </dependency>
300                     </dependencies>
301 
302                 </project>
303                 """;
304 
305     public static final String APP_JAVA_STRING =
306             """
307             package org.apache.maven.samples.sample;
308 
309             public class App {
310                 public static void main(String... args) {
311                     System.out.println("Hello World!");
312                 }
313             }
314             """;
315 
316     protected void execute(@Nullable Path logFile, Collection<ExecutorRequest> requests) throws Exception {
317         Executor invoker = createAndMemoizeExecutor();
318         for (ExecutorRequest request : requests) {
319             MimirInfuser.infuse(request.userHomeDirectory());
320             int exitCode = invoker.execute(request);
321             if (exitCode != 0) {
322                 throw new FailedExecution(request, exitCode, logFile == null ? "" : Files.readString(logFile));
323             }
324         }
325     }
326 
327     protected String mavenVersion(ExecutorRequest request) throws Exception {
328         return createAndMemoizeExecutor().mavenVersion(request);
329     }
330 
331     public static ExecutorRequest.Builder mvn3ExecutorRequestBuilder() {
332         return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven3home")));
333     }
334 
335     public static ExecutorRequest.Builder mvn4ExecutorRequestBuilder() {
336         return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven4home")));
337     }
338 
339     protected void layDownFiles(Path cwd) throws IOException {
340         Files.createDirectory(cwd.resolve(".mvn"));
341         Path pom = cwd.resolve("pom.xml").toAbsolutePath();
342         Files.writeString(pom, POM_STRING);
343         Path appJava = cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java");
344         Files.createDirectories(appJava.getParent());
345         Files.writeString(appJava, APP_JAVA_STRING);
346     }
347 
348     protected static class FailedExecution extends Exception {
349         private final ExecutorRequest request;
350         private final int exitCode;
351         private final String log;
352 
353         public FailedExecution(ExecutorRequest request, int exitCode, String log) {
354             super(request.toString() + " => " + exitCode + "\n" + log);
355             this.request = request;
356             this.exitCode = exitCode;
357             this.log = log;
358         }
359 
360         public ExecutorRequest getRequest() {
361             return request;
362         }
363 
364         public int getExitCode() {
365             return exitCode;
366         }
367 
368         public String getLog() {
369             return log;
370         }
371     }
372 
373     private static Executor executor;
374 
375     protected final Executor createAndMemoizeExecutor() {
376         if (executor == null) {
377             executor = doSelectExecutor();
378         }
379         return executor;
380     }
381 
382     @AfterAll
383     static void afterAll() {
384         if (executor != null) {
385             executor = null;
386         }
387     }
388 
389     // NOTE: we keep these instances alive to make sure JVM (running tests) loads JAnsi/JLine native library ONLY once
390     // in real life you'd anyway keep these alive as long needed, but here, we repeat a series of tests against same
391     // instance, to prevent them attempting native load more than once.
392     public static final EmbeddedMavenExecutor EMBEDDED_MAVEN_EXECUTOR = new EmbeddedMavenExecutor();
393     public static final ForkedMavenExecutor FORKED_MAVEN_EXECUTOR = new ForkedMavenExecutor();
394 
395     protected abstract Executor doSelectExecutor();
396 }