View Javadoc
1   package org.codehaus.plexus.compiler.javac;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.IOException;
6   import java.io.StringReader;
7   import java.nio.charset.StandardCharsets;
8   import java.nio.file.Files;
9   import java.nio.file.Path;
10  import java.util.Arrays;
11  import java.util.List;
12  import java.util.stream.Stream;
13  
14  import org.codehaus.plexus.compiler.CompilerConfiguration;
15  import org.codehaus.plexus.compiler.CompilerMessage;
16  import org.codehaus.plexus.compiler.CompilerResult;
17  import org.codehaus.plexus.compiler.javac.JavacCompiler.JavaVersion;
18  import org.junit.jupiter.api.BeforeEach;
19  import org.junit.jupiter.api.Test;
20  import org.junit.jupiter.api.io.TempDir;
21  import org.junit.jupiter.params.ParameterizedTest;
22  import org.junit.jupiter.params.provider.Arguments;
23  import org.junit.jupiter.params.provider.MethodSource;
24  
25  import static org.codehaus.plexus.compiler.javac.JavacCompiler.Messages.*;
26  import static org.junit.jupiter.api.Assertions.assertEquals;
27  import static org.junit.jupiter.api.Assertions.assertFalse;
28  import static org.junit.jupiter.api.Assertions.assertNotNull;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  /*
32   * Licensed to the Apache Software Foundation (ASF) under one
33   * or more contributor license agreements.  See the NOTICE file
34   * distributed with this work for additional information
35   * regarding copyright ownership.  The ASF licenses this file
36   * to you under the Apache License, Version 2.0 (the
37   * "License"); you may not use this file except in compliance
38   * with the License.  You may obtain a copy of the License at
39   *
40   *   http://www.apache.org/licenses/LICENSE-2.0
41   *
42   * Unless required by applicable law or agreed to in writing,
43   * software distributed under the License is distributed on an
44   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
45   * KIND, either express or implied.  See the License for the
46   * specific language governing permissions and limitations
47   * under the License.
48   */
49  
50  /**
51   * @author Olivier Lamy
52   * @author Alexander Kriegisch
53   */
54  public class JavacCompilerTest extends AbstractJavacCompilerTest {
55      private static final String EOL = System.getProperty("line.separator");
56      private static final String UNIDENTIFIABLE_LOG_LINES =
57              "These log lines should be cut off\n" + "when preceding known error message headers\n";
58  
59      @BeforeEach
60      public void setUp() {
61          super.setUp();
62          setForceJavacCompilerUse(true);
63      }
64  
65      @ParameterizedTest(name = "{0}")
66      @MethodSource("testParseModernStream_withAnnotationProcessingErrors_args")
67      void testParseModernStream_withAnnotationProcessingErrors(String jdkAndLocale, String stackTraceHeader)
68              throws IOException {
69          String stackTraceWithHeader = UNIDENTIFIABLE_LOG_LINES + stackTraceHeader + stackTraceAnnotationProcessingError;
70          List<CompilerMessage> compilerMessages =
71                  JavacCompiler.parseModernStream(1, new BufferedReader(new StringReader(stackTraceWithHeader)));
72  
73          assertNotNull(compilerMessages);
74          assertEquals(1, compilerMessages.size());
75  
76          String message = compilerMessages.get(0).getMessage().replaceAll(EOL, "\n");
77          // Parser retains stack trace header
78          assertTrue(message.startsWith(stackTraceHeader));
79          assertTrue(message.endsWith(stackTraceAnnotationProcessingError));
80      }
81  
82      private static final String stackTraceAnnotationProcessingError =
83              "java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x1da51a35) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x1da51a35\n"
84                      + "\tat lombok.javac.apt.LombokProcessor.getJavacProcessingEnvironment(LombokProcessor.java:433)\n"
85                      + "\tat lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:92)\n"
86                      + "\tat lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:160)\n"
87                      + "\tat lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:213)\n"
88                      + "\tat lombok.launch.AnnotationProcessorHider$AnnotationProcessor.init(AnnotationProcessor.java:64)\n"
89                      + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>(JavacProcessingEnvironment.java:702)\n"
90                      + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:829)\n"
91                      + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:925)\n"
92                      + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1269)\n"
93                      + "\tat jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1384)\n"
94                      + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1261)\n"
95                      + "\tat jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:935)\n"
96                      + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:317)\n"
97                      + "\tat jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:176)\n"
98                      + "\tat jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:64)\n"
99                      + "\tat jdk.compiler/com.sun.tools.javac.Main.main(Main.java:50)\n";
100 
101     private static Stream<Arguments> testParseModernStream_withAnnotationProcessingErrors_args() {
102         return Stream.of(
103                 Arguments.of("JDK 8 English", ANNOTATION_PROCESSING_ERROR_HEADERS[0]),
104                 Arguments.of("JDK 8 Japanese", ANNOTATION_PROCESSING_ERROR_HEADERS[1]),
105                 Arguments.of("JDK 8 Chinese", ANNOTATION_PROCESSING_ERROR_HEADERS[2]),
106                 Arguments.of("JDK 21 English", ANNOTATION_PROCESSING_ERROR_HEADERS[3]),
107                 Arguments.of("JDK 21 Japanese", ANNOTATION_PROCESSING_ERROR_HEADERS[4]),
108                 Arguments.of("JDK 21 Chinese", ANNOTATION_PROCESSING_ERROR_HEADERS[5]),
109                 Arguments.of("JDK 21 German", ANNOTATION_PROCESSING_ERROR_HEADERS[6]));
110     }
111 
112     @Test
113     void testJavaVersionPrefixes() {
114         assertFalse(JavaVersion.JAVA_1_4.isOlderOrEqualTo("1.3"));
115         assertTrue(JavaVersion.JAVA_1_4.isOlderOrEqualTo("1.4"));
116         assertTrue(JavaVersion.JAVA_1_4.isOlderOrEqualTo("1.4.0_something"));
117         assertFalse(JavaVersion.JAVA_1_5.isOlderOrEqualTo("1.4"));
118         assertTrue(JavaVersion.JAVA_1_8.isOlderOrEqualTo("1.8"));
119         assertTrue(JavaVersion.JAVA_1_8.isOlderOrEqualTo("22.0.2-something"));
120         assertTrue(JavaVersion.JAVA_1_8.isOlderOrEqualTo("unknown"));
121     }
122 
123     @Test
124     void testExtractMajorAndMinorVersion() {
125         assertEquals("11.0", JavacCompiler.extractMajorAndMinorVersion("javac 11.0.22"));
126         assertEquals("11.0", JavacCompiler.extractMajorAndMinorVersion("11.0.22"));
127         assertEquals("21", JavacCompiler.extractMajorAndMinorVersion("javac 21"));
128         assertEquals("1.8", JavacCompiler.extractMajorAndMinorVersion("javac 1.8.0_432"));
129     }
130 
131     @Test
132     void testForkedAndInProcessDiagnosticsAreEqual(@TempDir Path tempDirectory) throws Exception {
133         Path sourceDirectory = tempDirectory.resolve("src");
134         Files.createDirectories(sourceDirectory);
135         Files.write(
136                 sourceDirectory.resolve("Test.java"),
137                 Arrays.asList("class Test {", "Object foo() {", " null;", "}", "}"),
138                 StandardCharsets.UTF_8);
139 
140         CompilerResult inProcess = compile(tempDirectory, sourceDirectory, false);
141         CompilerResult forked = compile(tempDirectory, sourceDirectory, true);
142 
143         assertFalse(inProcess.isSuccess());
144         assertFalse(forked.isSuccess());
145         assertEquals(1, inProcess.getCompilerMessages().size());
146         assertEquals(1, forked.getCompilerMessages().size());
147 
148         CompilerMessage inProcessMessage = inProcess.getCompilerMessages().get(0);
149         CompilerMessage forkedMessage = forked.getCompilerMessages().get(0);
150         assertEquals(getFileName(inProcessMessage.getFile()), getFileName(forkedMessage.getFile()));
151         assertEquals(inProcessMessage.getStartLine(), forkedMessage.getStartLine());
152         assertEquals(inProcessMessage.getStartColumn(), forkedMessage.getStartColumn());
153         assertEquals(inProcessMessage.getMessage(), forkedMessage.getMessage());
154     }
155 
156     private static String getFileName(String path) {
157         int separator = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
158         return path.substring(separator + 1);
159     }
160 
161     private CompilerResult compile(Path tempDirectory, Path sourceDirectory, boolean fork) throws Exception {
162         File buildDirectory =
163                 tempDirectory.resolve(fork ? "forked" : "in-process").toFile();
164         Files.createDirectories(buildDirectory.toPath());
165 
166         CompilerConfiguration configuration = new CompilerConfiguration();
167         configuration.setFork(fork);
168         configuration.setWorkingDirectory(tempDirectory.toFile());
169         configuration.setBuildDirectory(buildDirectory);
170         configuration.setOutputLocation(new File(buildDirectory, "classes").getAbsolutePath());
171         configuration.addSourceLocation(sourceDirectory.toString());
172         configuration.setSourceVersion("8");
173         configuration.setTargetVersion("8");
174         configuration.addCompilerCustomArgument("-Xlint:-options", null);
175 
176         return getCompiler().performCompile(configuration);
177     }
178 
179     @ParameterizedTest(name = "{0}")
180     @MethodSource("quoteArgumentArguments")
181     void quoteArgument(String description, String argument, String expected) {
182         assertEquals(expected, JavacCompiler.quoteArgument(argument));
183     }
184 
185     private static Stream<Arguments> quoteArgumentArguments() {
186         return Stream.of(
187                 Arguments.of("empty", "", "\"\""),
188                 Arguments.of("plain", "-Xlint", "\"-Xlint\""),
189                 Arguments.of("spaces", "two words", "\"two words\""),
190                 Arguments.of("double quote", "say \"hello\"", "\"say \\\"hello\\\"\""),
191                 Arguments.of(
192                         "Windows path",
193                         "C:\\Program Files\\Java\\bin\\javac.exe",
194                         "\"C:\\\\Program Files\\\\Java\\\\bin\\\\javac.exe\""),
195                 Arguments.of("trailing backslash", "C:\\temp\\", "\"C:\\\\temp\\\\\""),
196                 Arguments.of("literal backslash-n", "-Dvalue=\\n", "\"-Dvalue=\\\\n\""),
197                 Arguments.of(
198                         "control whitespace",
199                         "line1\nline2\rline3\tline4\fline5",
200                         "\"line1\\nline2\\rline3\\tline4\\fline5\""),
201                 Arguments.of("backslash and newline", "line1\\\nline2", "\"line1" + "\\\\" + "\\n" + "line2\""));
202     }
203 }