1 /* 2 * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8192920 8204588 8246774 8248843 8268869 8235876 27 * @summary Test source launcher 28 * @library /tools/lib 29 * @enablePreview 30 * @modules jdk.compiler/com.sun.tools.javac.api 31 * jdk.compiler/com.sun.tools.javac.launcher 32 * jdk.compiler/com.sun.tools.javac.main 33 * java.base/jdk.internal.classfile.impl 34 * java.base/jdk.internal.module 35 * @build toolbox.JavaTask toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox 36 * @run main SourceLauncherTest 37 */ 38 39 import java.lang.classfile.*; 40 import java.lang.classfile.attribute.ModuleResolutionAttribute; 41 import java.io.ByteArrayOutputStream; 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.OutputStream; 45 import java.io.PrintStream; 46 import java.io.PrintWriter; 47 import java.io.StringWriter; 48 import java.lang.reflect.InvocationTargetException; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.nio.file.Paths; 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.HashMap; 55 import java.util.Map; 56 import java.util.List; 57 import java.util.Properties; 58 import java.util.regex.Pattern; 59 import java.util.stream.Collectors; 60 61 import com.sun.tools.javac.launcher.SourceLauncher; 62 import com.sun.tools.javac.launcher.Fault; 63 64 import toolbox.JavaTask; 65 import toolbox.JavacTask; 66 import toolbox.Task; 67 import toolbox.TestRunner; 68 import toolbox.ToolBox; 69 70 import static jdk.internal.module.ClassFileConstants.WARN_INCUBATING; 71 72 public class SourceLauncherTest extends TestRunner { 73 public static void main(String... args) throws Exception { 74 SourceLauncherTest t = new SourceLauncherTest(); 75 t.runTests(m -> new Object[] { Paths.get(m.getName()) }); 76 } 77 78 SourceLauncherTest() { 79 super(System.err); 80 tb = new ToolBox(); 81 System.err.println("version: " + thisVersion); 82 } 83 84 private final ToolBox tb; 85 private static final String thisVersion = System.getProperty("java.specification.version"); 86 87 /* 88 * Positive tests. 89 */ 90 91 @Test 92 public void testHelloWorld(Path base) throws IOException { 93 tb.writeJavaFiles(base, 94 "import java.util.Arrays;\n" + 95 "class HelloWorld {\n" + 96 " public static void main(String... args) {\n" + 97 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 98 " }\n" + 99 "}"); 100 testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n"); 101 } 102 103 @Test 104 public void testHelloWorldInPackage(Path base) throws IOException { 105 tb.writeJavaFiles(base, 106 "package hello;\n" + 107 "import java.util.Arrays;\n" + 108 "class World {\n" + 109 " public static void main(String... args) {\n" + 110 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 111 " }\n" + 112 "}"); 113 testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n"); 114 } 115 116 @Test 117 public void testHelloWorldWithAux(Path base) throws IOException { 118 tb.writeJavaFiles(base, 119 "import java.util.Arrays;\n" + 120 "class HelloWorld {\n" + 121 " public static void main(String... args) {\n" + 122 " Aux.write(args);\n" + 123 " }\n" + 124 "}\n" + 125 "class Aux {\n" + 126 " static void write(String... args) {\n" + 127 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 128 " }\n" + 129 "}"); 130 testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n"); 131 } 132 133 @Test 134 public void testHelloWorldWithShebang(Path base) throws IOException { 135 tb.writeJavaFiles(base, 136 "#!/usr/bin/java --source " + thisVersion + "\n" + 137 "import java.util.Arrays;\n" + 138 "class HelloWorld {\n" + 139 " public static void main(String... args) {\n" + 140 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 141 " }\n" + 142 "}"); 143 Files.copy(base.resolve("HelloWorld.java"), base.resolve("HelloWorld")); 144 testSuccess(base.resolve("HelloWorld"), "Hello World! [1, 2, 3]\n"); 145 } 146 147 @Test 148 public void testNoAnnoProcessing(Path base) throws IOException { 149 Path annoSrc = base.resolve("annoSrc"); 150 tb.writeJavaFiles(annoSrc, 151 "import java.util.*;\n" + 152 "import javax.annotation.processing.*;\n" + 153 "import javax.lang.model.element.*;\n" + 154 "@SupportedAnnotationTypes(\"*\")\n" + 155 "public class AnnoProc extends AbstractProcessor {\n" + 156 " public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n" + 157 " throw new Error(\"Annotation processor should not be invoked\");\n" + 158 " }\n" + 159 "}\n"); 160 Path annoClasses = Files.createDirectories(base.resolve("classes")); 161 new JavacTask(tb) 162 .outdir(annoClasses) 163 .files(annoSrc.resolve("AnnoProc.java").toString()) 164 .run(); 165 Path serviceFile = annoClasses.resolve("META-INF").resolve("services") 166 .resolve("javax.annotation.processing.Processor"); 167 tb.writeFile(serviceFile, "AnnoProc"); 168 169 Path mainSrc = base.resolve("mainSrc"); 170 tb.writeJavaFiles(mainSrc, 171 "import java.util.Arrays;\n" + 172 "class HelloWorld {\n" + 173 " public static void main(String... args) {\n" + 174 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 175 " }\n" + 176 "}"); 177 178 List<String> javacArgs = List.of("-classpath", annoClasses.toString()); 179 List<String> classArgs = List.of("1", "2", "3"); 180 String expect = "Hello World! [1, 2, 3]\n"; 181 Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs); 182 checkEqual("stdout", r.stdOut, expect); 183 checkEmpty("stderr", r.stdErr); 184 checkNull("exception", r.exception); 185 } 186 187 @Test 188 public void testEnablePreview(Path base) throws IOException { 189 tb.writeJavaFiles(base, 190 "import java.util.Arrays;\n" + 191 "class HelloWorld {\n" + 192 " public static void main(String... args) {\n" + 193 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 194 " }\n" + 195 "}"); 196 197 String log = new JavaTask(tb) 198 .vmOptions("--enable-preview", "--source", thisVersion) 199 .className(base.resolve("HelloWorld.java").toString()) 200 .classArgs("1", "2", "3") 201 .run(Task.Expect.SUCCESS) 202 .getOutput(Task.OutputKind.STDOUT); 203 checkEqual("stdout", log.trim(), "Hello World! [1, 2, 3]"); 204 } 205 206 @Test 207 public void testCodeSource(Path base) throws IOException { 208 tb.writeJavaFiles(base, 209 "import java.net.URL;\n" + 210 "class ShowCodeSource {\n" + 211 " public static void main(String... args) {\n" + 212 " URL u = ShowCodeSource.class.getProtectionDomain().getCodeSource().getLocation();\n" + 213 " System.out.println(u);\n" + 214 " }\n" + 215 "}"); 216 217 Path file = base.resolve("ShowCodeSource.java"); 218 String log = new JavaTask(tb) 219 .className(file.toString()) 220 .run(Task.Expect.SUCCESS) 221 .getOutput(Task.OutputKind.STDOUT); 222 checkEqual("stdout", log.trim(), file.toAbsolutePath().toUri().toURL().toString()); 223 } 224 225 @Test 226 public void testSecurityManager(Path base) throws IOException { 227 Path sourceFile = base.resolve("HelloWorld.java"); 228 tb.writeJavaFiles(base, 229 "class HelloWorld {\n" + 230 " public static void main(String... args) {\n" + 231 " System.out.println(\"Hello World!\");\n" + 232 " }\n" + 233 "}"); 234 235 String log = new JavaTask(tb) 236 .vmOptions("-Djava.security.manager=default") 237 .className(sourceFile.toString()) 238 .run(Task.Expect.FAIL) 239 .getOutput(Task.OutputKind.STDERR); 240 checkContains("stderr", log, 241 "error: cannot use source-code launcher with a security manager enabled"); 242 } 243 244 @Test 245 public void testSystemProperty(Path base) throws IOException { 246 tb.writeJavaFiles(base, 247 "class ShowProperty {\n" + 248 " public static void main(String... args) {\n" + 249 " System.out.println(System.getProperty(\"jdk.launcher.sourcefile\"));\n" + 250 " }\n" + 251 "}"); 252 253 Path file = base.resolve("ShowProperty.java"); 254 String log = new JavaTask(tb) 255 .className(file.toString()) 256 .run(Task.Expect.SUCCESS) 257 .getOutput(Task.OutputKind.STDOUT); 258 checkEqual("stdout", log.trim(), file.toAbsolutePath().toString()); 259 } 260 261 void testSuccess(Path file, String expect) throws IOException { 262 Result r = run(file, Collections.emptyList(), List.of("1", "2", "3")); 263 checkEqual("stdout", r.stdOut, expect); 264 checkEmpty("stderr", r.stdErr); 265 checkNull("exception", r.exception); 266 } 267 268 /* 269 * Negative tests: such as cannot find or execute main method. 270 */ 271 272 @Test 273 public void testHelloWorldWithShebangJava(Path base) throws IOException { 274 tb.writeJavaFiles(base, 275 "#!/usr/bin/java --source " + thisVersion + "\n" + 276 "import java.util.Arrays;\n" + 277 "class HelloWorld {\n" + 278 " public static void main(String... args) {\n" + 279 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 280 " }\n" + 281 "}"); 282 Path file = base.resolve("HelloWorld.java"); 283 testError(file, 284 file + ":1: error: illegal character: '#'\n" + 285 "#!/usr/bin/java --source " + thisVersion + "\n" + 286 "^\n" + 287 file + ":1: error: class, interface, enum, or record expected\n" + 288 "#!/usr/bin/java --source " + thisVersion + "\n" + 289 " ^\n" + 290 "2 errors\n", 291 "error: compilation failed"); 292 } 293 294 @Test 295 public void testNoClass(Path base) throws IOException { 296 var path = Files.createDirectories(base.resolve("p")); 297 Path file = path.resolve("NoClass.java"); 298 Files.write(file, List.of("package p;")); 299 testError(file, "", "error: no class declared in source file"); 300 } 301 302 @Test 303 public void testMismatchOfPathAndPackage(Path base) throws IOException { 304 Files.createDirectories(base); 305 Path file = base.resolve("MismatchOfPathAndPackage.java"); 306 Files.write(file, List.of("package p;")); 307 testError(file, "", "error: end of path to source file does not match its package name p: " + file); 308 } 309 310 @Test 311 public void testLoadClass(Path base) throws IOException { 312 Path src1 = base.resolve("src1"); 313 Path file1 = src1.resolve("LoadClass.java"); 314 tb.writeJavaFiles(src1, 315 "class LoadClass {\n" 316 + " public static void main(String... args) {\n" 317 + " System.out.println(\"on classpath\");\n" 318 + " };\n" 319 + "}\n"); 320 Path classes1 = Files.createDirectories(base.resolve("classes")); 321 new JavacTask(tb) 322 .outdir(classes1) 323 .files(file1) 324 .run(); 325 String log1 = new JavaTask(tb) 326 .classpath(classes1.toString()) 327 .className("LoadClass") 328 .run(Task.Expect.SUCCESS) 329 .getOutput(Task.OutputKind.STDOUT); 330 checkEqual("stdout", log1.trim(), 331 "on classpath"); 332 333 Path src2 = base.resolve("src2"); 334 Path file2 = src2.resolve("LoadClass.java"); 335 tb.writeJavaFiles(src2, 336 "class LoadClass {\n" 337 + " public static void main(String... args) {\n" 338 + " System.out.println(\"in source file\");\n" 339 + " };\n" 340 + "}\n"); 341 String log2 = new JavaTask(tb) 342 .classpath(classes1.toString()) 343 .className(file2.toString()) 344 .run(Task.Expect.SUCCESS) 345 .getOutput(Task.OutputKind.STDOUT); 346 checkEqual("stdout", log2.trim(), 347 "in source file"); 348 } 349 350 @Test 351 public void testGetResource(Path base) throws IOException { 352 Path src = base.resolve("src"); 353 Path file = src.resolve("GetResource.java"); 354 tb.writeJavaFiles(src, 355 "class GetResource {\n" 356 + " public static void main(String... args) {\n" 357 + " System.out.println(GetResource.class.getClassLoader().getResource(\"GetResource.class\"));\n" 358 + " };\n" 359 + "}\n"); 360 Path classes = Files.createDirectories(base.resolve("classes")); 361 new JavacTask(tb) 362 .outdir(classes) 363 .files(file) 364 .run(); 365 366 String log = new JavaTask(tb) 367 .classpath(classes.toString()) 368 .className(file.toString()) 369 .run(Task.Expect.SUCCESS) 370 .getOutput(Task.OutputKind.STDOUT); 371 checkMatch("stdout", log.trim(), 372 Pattern.compile("sourcelauncher-memoryclassloader[0-9]+:GetResource.class")); 373 } 374 375 @Test 376 public void testGetResources(Path base) throws IOException { 377 Path src = base.resolve("src"); 378 Path file = src.resolve("GetResources.java"); 379 tb.writeJavaFiles(src, 380 "import java.io.*; import java.net.*; import java.util.*;\n" 381 + "class GetResources {\n" 382 + " public static void main(String... args) throws IOException {\n" 383 + " Enumeration<URL> e =\n" 384 + " GetResources.class.getClassLoader().getResources(\"GetResources.class\");\n" 385 + " while (e.hasMoreElements()) System.out.println(e.nextElement());\n" 386 + " };\n" 387 + "}\n"); 388 Path classes = Files.createDirectories(base.resolve("classes")); 389 new JavacTask(tb) 390 .outdir(classes) 391 .files(file) 392 .run(); 393 394 List<String> log = new JavaTask(tb) 395 .classpath(classes.toString()) 396 .className(file.toString()) 397 .run(Task.Expect.SUCCESS) 398 .getOutputLines(Task.OutputKind.STDOUT); 399 checkMatch("stdout:0", log.get(0).trim(), 400 Pattern.compile("sourcelauncher-memoryclassloader[0-9]+:GetResources.class")); 401 checkMatch("stdout:1", log.get(1).trim(), 402 Pattern.compile("file:/.*/testGetResources/classes/GetResources.class")); 403 } 404 405 @Test 406 public void testSyntaxErr(Path base) throws IOException { 407 tb.writeJavaFiles(base, "class SyntaxErr {"); 408 Path file = base.resolve("SyntaxErr.java"); 409 testError(file, 410 file + ":1: error: reached end of file while parsing\n" + 411 "class SyntaxErr {\n" + 412 " ^\n" + 413 "1 error\n", 414 "error: compilation failed"); 415 } 416 417 @Test 418 public void testNoSourceOnClassPath(Path base) throws IOException { 419 Path extraSrc = base.resolve("extraSrc"); 420 tb.writeJavaFiles(extraSrc, 421 "public class Extra {\n" + 422 " static final String MESSAGE = \"Hello World\";\n" + 423 "}\n"); 424 425 Path mainSrc = base.resolve("mainSrc"); 426 tb.writeJavaFiles(mainSrc, 427 "import java.util.Arrays;\n" + 428 "class HelloWorld {\n" + 429 " public static void main(String... args) {\n" + 430 " System.out.println(Extra.MESSAGE + Arrays.toString(args));\n" + 431 " }\n" + 432 "}"); 433 434 List<String> javacArgs = List.of("-classpath", extraSrc.toString()); 435 List<String> classArgs = List.of("1", "2", "3"); 436 String FS = File.separator; 437 String expectStdErr = 438 "testNoSourceOnClassPath" + FS + "mainSrc" + FS + "HelloWorld.java:4: error: cannot find symbol\n" + 439 " System.out.println(Extra.MESSAGE + Arrays.toString(args));\n" + 440 " ^\n" + 441 " symbol: variable Extra\n" + 442 " location: class HelloWorld\n" + 443 "1 error\n"; 444 Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs); 445 checkEmpty("stdout", r.stdOut); 446 checkEqual("stderr", r.stdErr, expectStdErr); 447 checkFault("exception", r.exception, "error: compilation failed"); 448 } 449 450 @Test 451 public void testClassNotFound(Path base) throws IOException { 452 Path src = base.resolve("src"); 453 Path file = src.resolve("ClassNotFound.java"); 454 tb.writeJavaFiles(src, 455 "class ClassNotFound {\n" 456 + " public static void main(String... args) {\n" 457 + " try {\n" 458 + " Class.forName(\"NoSuchClass\");\n" 459 + " System.out.println(\"no exception\");\n" 460 + " System.exit(1);\n" 461 + " } catch (ClassNotFoundException e) {\n" 462 + " System.out.println(\"Expected exception thrown: \" + e);\n" 463 + " }\n" 464 + " };\n" 465 + "}\n"); 466 Path classes = Files.createDirectories(base.resolve("classes")); 467 new JavacTask(tb) 468 .outdir(classes) 469 .files(file) 470 .run(); 471 472 String log = new JavaTask(tb) 473 .classpath(classes.toString()) 474 .className(file.toString()) 475 .run(Task.Expect.SUCCESS) 476 .getOutput(Task.OutputKind.STDOUT); 477 checkEqual("stdout", log.trim(), 478 "Expected exception thrown: java.lang.ClassNotFoundException: NoSuchClass"); 479 } 480 481 // For any source file that is invoked through the OS shebang mechanism, invalid shebang 482 // lines will be caught and handled by the OS, before the launcher is even invoked. 483 // However, if such a file is passed directly to the launcher, perhaps using the --source 484 // option, a well-formed shebang line will be removed but a badly-formed one will be not be 485 // removed and will cause compilation errors. 486 @Test 487 public void testBadShebang(Path base) throws IOException { 488 tb.writeJavaFiles(base, 489 "#/usr/bin/java --source " + thisVersion + "\n" + 490 "import java.util.Arrays;\n" + 491 "class HelloWorld {\n" + 492 " public static void main(String... args) {\n" + 493 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 494 " }\n" + 495 "}"); 496 Path file = base.resolve("HelloWorld.java"); 497 testError(file, 498 file + ":1: error: illegal character: '#'\n" + 499 "#/usr/bin/java --source " + thisVersion + "\n" + 500 "^\n" + 501 file + ":1: error: class, interface, enum, or record expected\n" + 502 "#/usr/bin/java --source " + thisVersion + "\n" + 503 " ^\n" + 504 "2 errors\n", 505 "error: compilation failed"); 506 } 507 508 @Test 509 public void testBadSourceOpt(Path base) throws IOException { 510 Files.createDirectories(base); 511 Path file = base.resolve("DummyClass.java"); 512 Files.write(file, List.of("class DummyClass { }")); 513 Properties sysProps = System.getProperties(); 514 Properties p = new Properties(sysProps); 515 p.setProperty("jdk.internal.javac.source", "<BAD>"); 516 System.setProperties(p); 517 try { 518 testError(file, "", "error: invalid value for --source option: <BAD>"); 519 } finally { 520 System.setProperties(sysProps); 521 } 522 } 523 524 @Test 525 public void testEnablePreviewNoSource(Path base) throws IOException { 526 tb.writeJavaFiles(base, 527 "import java.util.Arrays;\n" + 528 "class HelloWorld {\n" + 529 " public static void main(String... args) {\n" + 530 " System.out.println(\"Hello World! \" + Arrays.toString(args));\n" + 531 " }\n" + 532 "}"); 533 534 List<String> log = new JavaTask(tb) 535 .vmOptions("--enable-preview") 536 .className(base.resolve("HelloWorld.java").toString()) 537 .run(Task.Expect.FAIL) 538 .getOutputLines(Task.OutputKind.STDERR); 539 log = log.stream().filter(s->!s.matches("^Picked up .*JAVA.*OPTIONS:.*")).collect(Collectors.toList()); 540 checkEqual("stderr", log, List.of("error: --enable-preview must be used with --source")); 541 } 542 543 @Test 544 public void testNoMain(Path base) throws IOException { 545 tb.writeJavaFiles(base, "class NoMain { }"); 546 testError(base.resolve("NoMain.java"), "", 547 "error: can't find main(String[]) method in class: NoMain"); 548 } 549 550 //@Test temporary disabled as enabled preview allows no-param main 551 public void testMainBadParams(Path base) throws IOException { 552 tb.writeJavaFiles(base, 553 "class BadParams { public static void main() { } }"); 554 testError(base.resolve("BadParams.java"), "", 555 "error: can't find main(String[]) method in class: BadParams"); 556 } 557 558 //@Test temporary disabled as enabled preview allows non-public main 559 public void testMainNotPublic(Path base) throws IOException { 560 tb.writeJavaFiles(base, 561 "class NotPublic { static void main(String... args) { } }"); 562 testError(base.resolve("NotPublic.java"), "", 563 "error: can't find main(String[]) method in class: NotPublic"); 564 } 565 566 //@Test temporary disabled as enabled preview allows non-static main 567 public void testMainNotStatic(Path base) throws IOException { 568 tb.writeJavaFiles(base, 569 "class NotStatic { public void main(String... args) { } }"); 570 testError(base.resolve("NotStatic.java"), "", 571 "error: can't find main(String[]) method in class: NotStatic"); 572 } 573 574 @Test 575 public void testMainNotVoid(Path base) throws IOException { 576 tb.writeJavaFiles(base, 577 "class NotVoid { public static int main(String... args) { return 0; } }"); 578 testError(base.resolve("NotVoid.java"), "", 579 "error: can't find main(String[]) method in class: NotVoid"); 580 } 581 582 @Test 583 public void testClassInModule(Path base) throws IOException { 584 tb.writeJavaFiles(base, "package java.net; class InModule { }"); 585 Path file = base.resolve("java").resolve("net").resolve("InModule.java"); 586 testError(file, 587 file + ":1: error: package exists in another module: java.base\n" + 588 "package java.net; class InModule { }\n" + 589 "^\n" + 590 "1 error\n", 591 "error: compilation failed"); 592 } 593 594 @Test 595 public void testNoRecompileWithSuggestions(Path base) throws IOException { 596 tb.writeJavaFiles(base, 597 "class NoRecompile {\n" + 598 " void use(String s) {}\n" + 599 " void test() {\n" + 600 " use(1);\n" + 601 " }\n" + 602 " <T> void test(T t, Object o) {\n" + 603 " T t1 = (T) o;\n" + 604 " }\n" + 605 " static class Generic<T> {\n" + 606 " T t;\n" + 607 " void raw(Generic raw) {\n" + 608 " raw.t = \"\";\n" + 609 " }\n" + 610 " }\n" + 611 " void deprecation() {\n" + 612 " Thread.currentThread().stop();\n" + 613 " }\n" + 614 " void preview(Object o) {\n" + 615 " if (o instanceof String s) {\n" + 616 " System.out.println(s);\n" + 617 " }\n" + 618 " }\n" + 619 "}"); 620 Result r = run(base.resolve("NoRecompile.java"), Collections.emptyList(), Collections.emptyList()); 621 if (r.stdErr.contains("recompile with")) { 622 error("Unexpected recompile suggestions in error output: " + r.stdErr); 623 } 624 } 625 626 @Test 627 public void testNoOptionsWarnings(Path base) throws IOException { 628 tb.writeJavaFiles(base, "public class Main { public static void main(String... args) {}}"); 629 String log = new JavaTask(tb) 630 .vmOptions("--source", "21") 631 .className(base.resolve("Main.java").toString()) 632 .run(Task.Expect.SUCCESS) 633 .getOutput(Task.OutputKind.STDERR); 634 635 if (log.contains("warning: [options]")) { 636 error("Unexpected options warning in error output: " + log); 637 } 638 } 639 640 void testError(Path file, String expectStdErr, String expectFault) throws IOException { 641 Result r = run(file, Collections.emptyList(), List.of("1", "2", "3")); 642 checkEmpty("stdout", r.stdOut); 643 checkEqual("stderr", r.stdErr, expectStdErr); 644 checkFault("exception", r.exception, expectFault); 645 } 646 647 /* 648 * Tests in which main throws an exception. 649 */ 650 @Test 651 public void testTargetException1(Path base) throws IOException { 652 tb.writeJavaFiles(base, 653 "import java.util.Arrays;\n" + 654 "class Thrower {\n" + 655 " public static void main(String... args) {\n" + 656 " throwWhenZero(Integer.parseInt(args[0]));\n" + 657 " }\n" + 658 " static void throwWhenZero(int arg) {\n" + 659 " if (arg == 0) throw new Error(\"zero!\");\n" + 660 " throwWhenZero(arg - 1);\n" + 661 " }\n" + 662 "}"); 663 Path file = base.resolve("Thrower.java"); 664 Result r = run(file, Collections.emptyList(), List.of("3")); 665 checkEmpty("stdout", r.stdOut); 666 checkEmpty("stderr", r.stdErr); 667 checkTrace("exception", r.exception, 668 "java.lang.Error: zero!", 669 "at Thrower.throwWhenZero(Thrower.java:7)", 670 "at Thrower.throwWhenZero(Thrower.java:8)", 671 "at Thrower.throwWhenZero(Thrower.java:8)", 672 "at Thrower.throwWhenZero(Thrower.java:8)", 673 "at Thrower.main(Thrower.java:4)"); 674 } 675 676 @Test 677 public void testNoDuplicateIncubatorWarning(Path base) throws Exception { 678 Path module = base.resolve("lib"); 679 Path moduleSrc = module.resolve("src"); 680 Path moduleClasses = module.resolve("classes"); 681 Files.createDirectories(moduleClasses); 682 tb.cleanDirectory(moduleClasses); 683 tb.writeJavaFiles(moduleSrc, "module test {}"); 684 new JavacTask(tb) 685 .outdir(moduleClasses) 686 .files(tb.findJavaFiles(moduleSrc)) 687 .run() 688 .writeAll(); 689 markModuleAsIncubator(moduleClasses.resolve("module-info.class")); 690 tb.writeJavaFiles(base, "public class Main { public static void main(String... args) {}}"); 691 String log = new JavaTask(tb) 692 .vmOptions("--module-path", moduleClasses.toString(), 693 "--add-modules", "test") 694 .className(base.resolve("Main.java").toString()) 695 .run(Task.Expect.SUCCESS) 696 .writeAll() 697 .getOutput(Task.OutputKind.STDERR); 698 699 int numberOfWarnings = log.split("WARNING").length - 1; 700 701 if (log.contains("warning:") || numberOfWarnings != 1) { 702 error("Unexpected warning in error output: " + log); 703 } 704 705 List<String> compileLog = new JavacTask(tb) 706 .options("--module-path", moduleClasses.toString(), 707 "--add-modules", "test", 708 "-XDrawDiagnostics", 709 "-XDsourceLauncher", 710 "-XDshould-stop.at=FLOW") 711 .files(base.resolve("Main.java").toString()) 712 .run(Task.Expect.SUCCESS) 713 .writeAll() 714 .getOutputLines(Task.OutputKind.DIRECT); 715 716 List<String> expectedOutput = List.of( 717 "- compiler.warn.incubating.modules: test", 718 "1 warning" 719 ); 720 721 if (!expectedOutput.equals(compileLog)) { 722 error("Unexpected options : " + compileLog); 723 } 724 } 725 //where: 726 private static void markModuleAsIncubator(Path moduleInfoFile) throws Exception { 727 ClassModel cf = ClassFile.of().parse(moduleInfoFile); 728 ModuleResolutionAttribute newAttr = ModuleResolutionAttribute.of(WARN_INCUBATING); 729 byte[] newBytes = ClassFile.of().transform(cf, ClassTransform.dropping(ce -> ce instanceof Attributes) 730 .andThen(ClassTransform.endHandler(classBuilder -> classBuilder.with(newAttr)))); 731 try (OutputStream out = Files.newOutputStream(moduleInfoFile)) { 732 out.write(newBytes); 733 } 734 } 735 736 Result run(Path file, List<String> runtimeArgs, List<String> appArgs) { 737 List<String> args = new ArrayList<>(); 738 args.add(file.toString()); 739 args.addAll(appArgs); 740 741 PrintStream prev = System.out; 742 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 743 try (PrintStream out = new PrintStream(baos, true)) { 744 System.setOut(out); 745 StringWriter sw = new StringWriter(); 746 try (PrintWriter err = new PrintWriter(sw, true)) { 747 SourceLauncher m = new SourceLauncher(err); 748 m.run(toArray(runtimeArgs), toArray(args)); 749 return new Result(baos.toString(), sw.toString(), null); 750 } catch (Throwable t) { 751 return new Result(baos.toString(), sw.toString(), t); 752 } 753 } finally { 754 System.setOut(prev); 755 } 756 } 757 758 void checkEqual(String name, String found, String expect) { 759 expect = expect.replace("\n", tb.lineSeparator); 760 out.println(name + ": " + found); 761 if (!expect.equals(found)) { 762 error("Unexpected output; expected: " + expect); 763 } 764 } 765 766 void checkContains(String name, String found, String expect) { 767 expect = expect.replace("\n", tb.lineSeparator); 768 out.println(name + ": " + found); 769 if (!found.contains(expect)) { 770 error("Expected output not found: " + expect); 771 } 772 } 773 774 void checkEqual(String name, List<String> found, List<String> expect) { 775 out.println(name + ": " + found); 776 tb.checkEqual(expect, found); 777 } 778 779 void checkMatch(String name, String found, Pattern expect) { 780 out.println(name + ": " + found); 781 if (!expect.matcher(found).matches()) { 782 error("Unexpected output; expected match for: " + expect); 783 } 784 } 785 786 void checkEmpty(String name, String found) { 787 out.println(name + ": " + found); 788 if (!found.isEmpty()) { 789 error("Unexpected output; expected empty string"); 790 } 791 } 792 793 void checkNull(String name, Throwable found) { 794 out.println(name + ": " + found); 795 if (found != null) { 796 error("Unexpected exception; expected null"); 797 } 798 } 799 800 void checkFault(String name, Throwable found, String expect) { 801 expect = expect.replace("\n", tb.lineSeparator); 802 out.println(name + ": " + found); 803 if (found == null) { 804 error("No exception thrown; expected Fault"); 805 } else { 806 if (!(found instanceof Fault)) { 807 error("Unexpected exception; expected Fault"); 808 } 809 if (!(found.getMessage().equals(expect))) { 810 error("Unexpected detail message; expected: " + expect); 811 } 812 } 813 } 814 815 void checkTrace(String name, Throwable found, String... expect) { 816 if (!(found instanceof InvocationTargetException)) { 817 error("Unexpected exception; expected InvocationTargetException"); 818 out.println("Found:"); 819 found.printStackTrace(out); 820 } 821 StringWriter sw = new StringWriter(); 822 try (PrintWriter pw = new PrintWriter(sw)) { 823 ((InvocationTargetException) found).getTargetException().printStackTrace(pw); 824 } 825 String trace = sw.toString(); 826 out.println(name + ":\n" + trace); 827 String[] traceLines = trace.trim().split("[\r\n]+\\s+"); 828 try { 829 tb.checkEqual(List.of(traceLines), List.of(expect)); 830 } catch (Error e) { 831 error(e.getMessage()); 832 } 833 } 834 835 String[] toArray(List<String> list) { 836 return list.toArray(new String[list.size()]); 837 } 838 839 record Result(String stdOut, String stdErr, Throwable exception) {} 840 }