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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
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 }