1 /* 2 * Copyright (c) 2017, 2018, 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 8210275 27 * @summary Test source mode 28 * @modules jdk.compiler jdk.jlink 29 * @run main SourceMode 30 */ 31 32 33 import java.io.IOException; 34 import java.io.PrintStream; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.nio.file.attribute.PosixFilePermission; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.spi.ToolProvider; 46 47 public class SourceMode extends TestHelper { 48 49 public static void main(String... args) throws Exception { 50 new SourceMode().run(args); 51 } 52 53 // To reduce the chance of creating shebang lines that are too long, 54 // use a shorter path for a java command if the standard path is too long. 55 private final Path shebangJavaCmd; 56 57 // Whether or not to automatically skip the shebang tests 58 private final boolean skipShebangTest; 59 60 private final PrintStream log; 61 62 private static final String thisVersion = System.getProperty("java.specification.version"); 63 64 SourceMode() throws Exception { 65 log = System.err; 66 67 if (isWindows) { 68 // Skip shebang tests on Windows, because that requires Cygwin. 69 skipShebangTest = true; 70 shebangJavaCmd = null; 71 } else { 72 // Try to ensure the path to the Java launcher is reasonably short, 73 // to work around the mostly undocumented limit of 120 characters 74 // for a shebang line. 75 // The value of 120 is the typical kernel compile-time buffer limit. 76 // The following limit of 80 allows room for arguments to be placed 77 // after the path to the launcher on the shebang line. 78 Path cmd = Paths.get(javaCmd); 79 if (cmd.toString().length() < 80) { 80 shebangJavaCmd = cmd; 81 } else { 82 // Create a small image in the current directory, such that 83 // the path for the launcher is just "tmpJDK/bin/java". 84 Path tmpJDK = Paths.get("tmpJDK"); 85 ToolProvider jlink = ToolProvider.findFirst("jlink") 86 .orElseThrow(() -> new Exception("cannot find jlink")); 87 jlink.run(System.out, System.err, 88 "--add-modules", "jdk.compiler,jdk.zipfs", "--output", tmpJDK.toString()); 89 shebangJavaCmd = tmpJDK.resolve("bin").resolve("java"); 90 } 91 log.println("Using java command: " + shebangJavaCmd); 92 skipShebangTest = false; 93 } 94 } 95 96 // java Simple.java 1 2 3 97 @Test 98 void testSimpleJava() throws IOException { 99 starting("testSimpleJava"); 100 Path file = getSimpleFile("Simple.java", false); 101 TestResult tr = doExec(javaCmd, file.toString(), "1", "2", "3"); 102 if (!tr.isOK()) 103 error(tr, "Bad exit code: " + tr.exitValue); 104 if (!tr.contains("[1, 2, 3]")) 105 error(tr, "Expected output not found"); 106 show(tr); 107 } 108 109 // java --source N simple 1 2 3 110 @Test 111 void testSimple() throws IOException { 112 starting("testSimple"); 113 Path file = getSimpleFile("simple", false); 114 TestResult tr = doExec(javaCmd, "--source", thisVersion, file.toString(), "1", "2", "3"); 115 if (!tr.isOK()) 116 error(tr, "Bad exit code: " + tr.exitValue); 117 if (!tr.contains("[1, 2, 3]")) 118 error(tr, "Expected output not found"); 119 show(tr); 120 } 121 122 // execSimple 1 2 3 123 @Test 124 void testExecSimple() throws IOException { 125 starting("testExecSimple"); 126 if (skipShebangTest) { 127 log.println("SKIPPED"); 128 return; 129 } 130 Path file = setExecutable(getSimpleFile("execSimple", true)); 131 TestResult tr = doExec(file.toAbsolutePath().toString(), "1", "2", "3"); 132 if (!tr.isOK()) 133 error(tr, "Bad exit code: " + tr.exitValue); 134 if (!tr.contains("[1, 2, 3]")) 135 error(tr, "Expected output not found"); 136 show(tr); 137 } 138 139 // java @simpleJava.at (contains Simple.java 1 2 3) 140 @Test 141 void testSimpleJavaAtFile() throws IOException { 142 starting("testSimpleJavaAtFile"); 143 Path file = getSimpleFile("Simple.java", false); 144 Path atFile = Paths.get("simpleJava.at"); 145 createFile(atFile, List.of(file + " 1 2 3")); 146 TestResult tr = doExec(javaCmd, "@" + atFile); 147 if (!tr.isOK()) 148 error(tr, "Bad exit code: " + tr.exitValue); 149 if (!tr.contains("[1, 2, 3]")) 150 error(tr, "Expected output not found"); 151 show(tr); 152 } 153 154 // java @simple.at (contains --source N simple 1 2 3) 155 @Test 156 void testSimpleAtFile() throws IOException { 157 starting("testSimpleAtFile"); 158 Path file = getSimpleFile("simple", false); 159 Path atFile = Paths.get("simple.at"); 160 createFile(atFile, List.of("--source " + thisVersion + " " + file + " 1 2 3")); 161 TestResult tr = doExec(javaCmd, "@" + atFile); 162 if (!tr.isOK()) 163 error(tr, "Bad exit code: " + tr.exitValue); 164 if (!tr.contains("[1, 2, 3]")) 165 error(tr, "Expected output not found"); 166 show(tr); 167 } 168 169 // java -cp classes Main.java 1 2 3 170 @Test 171 void testClasspath() throws IOException { 172 starting("testClasspath"); 173 Path base = Files.createDirectories(Paths.get("testClasspath")); 174 Path otherJava = base.resolve("Other.java"); 175 createFile(otherJava, List.of( 176 "public class Other {", 177 " public static String join(String[] args) {", 178 " return String.join(\"-\", args);", 179 " }", 180 "}" 181 )); 182 Path classes = Files.createDirectories(base.resolve("classes")); 183 Path mainJava = base.resolve("Main.java"); 184 createFile(mainJava, List.of( 185 "class Main {", 186 " public static void main(String[] args) {", 187 " System.out.println(Other.join(args));", 188 " }}" 189 )); 190 compile("-d", classes.toString(), otherJava.toString()); 191 TestResult tr = doExec(javaCmd, "-cp", classes.toString(), 192 mainJava.toString(), "1", "2", "3"); 193 if (!tr.isOK()) 194 error(tr, "Bad exit code: " + tr.exitValue); 195 if (!tr.contains("1-2-3")) 196 error(tr, "Expected output not found"); 197 show(tr); 198 } 199 200 // java --add-exports=... Export.java --help 201 @Test 202 void testAddExports() throws IOException { 203 starting("testAddExports"); 204 Path exportJava = Paths.get("Export.java"); 205 createFile(exportJava, List.of( 206 "public class Export {", 207 " public static void main(String[] args) {", 208 " new com.sun.tools.javac.main.Main(\"demo\").compile(args);", 209 " }", 210 "}" 211 )); 212 // verify access fails without --add-exports 213 TestResult tr1 = doExec(javaCmd, exportJava.toString(), "--help"); 214 if (tr1.isOK()) 215 error(tr1, "Compilation succeeded unexpectedly"); 216 show(tr1); 217 // verify access succeeds with --add-exports 218 TestResult tr2 = doExec(javaCmd, 219 "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", 220 exportJava.toString(), "--help"); 221 if (!tr2.isOK()) 222 error(tr2, "Bad exit code: " + tr2.exitValue); 223 if (!(tr2.contains("demo") && tr2.contains("Usage"))) 224 error(tr2, "Expected output not found"); 225 show(tr2); 226 } 227 228 // java -cp ... HelloWorld.java (for a class "java" in package "HelloWorld") 229 @Test 230 void testClassNamedJava() throws IOException { 231 starting("testClassNamedJava"); 232 Path base = Files.createDirectories(Paths.get("testClassNamedJava")); 233 Path src = Files.createDirectories(base.resolve("src")); 234 Path srcfile = src.resolve("java.java"); 235 createFile(srcfile, List.of( 236 "package HelloWorld;", 237 "class java {", 238 " public static void main(String... args) {", 239 " System.out.println(HelloWorld.java.class.getName());", 240 " }", 241 "}" 242 )); 243 Path classes = base.resolve("classes"); 244 compile("-d", classes.toString(), srcfile.toString()); 245 TestResult tr = 246 doExec(javaCmd, "-cp", classes.toString(), "HelloWorld.java"); 247 if (!tr.isOK()) 248 error(tr, "Command failed"); 249 if (!tr.contains("HelloWorld.java")) 250 error(tr, "Expected output not found"); 251 show(tr); 252 } 253 254 // java --source N -cp ... HelloWorld 255 @Test 256 void testSourceClasspath() throws IOException { 257 starting("testSourceClasspath"); 258 Path base = Files.createDirectories(Paths.get("testSourceClasspath")); 259 Path src = Files.createDirectories(base.resolve("src")); 260 Path srcfile = src.resolve("java.java"); 261 createFile(srcfile, List.of( 262 "class HelloWorld {", 263 " public static void main(String... args) {", 264 " System.out.println(\"Hello World\");", 265 " }", 266 "}" 267 )); 268 Path classes = base.resolve("classes"); 269 compile("-d", classes.toString(), srcfile.toString()); 270 TestResult tr = 271 doExec(javaCmd, "--source", thisVersion, "-cp", classes.toString(), "HelloWorld"); 272 if (tr.isOK()) 273 error(tr, "Command succeeded unexpectedly"); 274 if (!tr.contains("file not found: HelloWorld")) 275 error(tr, "Expected output not found"); 276 show(tr); 277 } 278 279 // java --source 280 @Test 281 void testSourceNoArg() throws IOException { 282 starting("testSourceNoArg"); 283 TestResult tr = doExec(javaCmd, "--source"); 284 if (tr.isOK()) 285 error(tr, "Command succeeded unexpectedly"); 286 if (!tr.contains("--source requires source version")) 287 error(tr, "Expected output not found"); 288 show(tr); 289 } 290 291 // java --source N -jar simple.jar 292 @Test 293 void testSourceJarConflict() throws IOException { 294 starting("testSourceJarConflict"); 295 Path base = Files.createDirectories(Paths.get("testSourceJarConflict")); 296 Path file = getSimpleFile("Simple.java", false); 297 Path classes = Files.createDirectories(base.resolve("classes")); 298 compile("-d", classes.toString(), file.toString()); 299 Path simpleJar = base.resolve("simple.jar"); 300 createJar("cf", simpleJar.toString(), "-C", classes.toString(), "."); 301 TestResult tr = 302 doExec(javaCmd, "--source", thisVersion, "-jar", simpleJar.toString()); 303 if (tr.isOK()) 304 error(tr, "Command succeeded unexpectedly"); 305 if (!tr.contains("Option -jar is not allowed with --source")) 306 error(tr, "Expected output not found"); 307 show(tr); 308 } 309 310 // java --source N -m jdk.compiler 311 @Test 312 void testSourceModuleConflict() throws IOException { 313 starting("testSourceModuleConflict"); 314 TestResult tr = doExec(javaCmd, "--source", thisVersion, "-m", "jdk.compiler"); 315 if (tr.isOK()) 316 error(tr, "Command succeeded unexpectedly"); 317 if (!tr.contains("Option -m is not allowed with --source")) 318 error(tr, "Expected output not found"); 319 show(tr); 320 } 321 322 // #!.../java --source N -version 323 @Test 324 void testTerminalOptionInShebang() throws IOException { 325 starting("testTerminalOptionInShebang"); 326 if (skipShebangTest || isAIX || isMacOSX) { 327 // On MacOSX, we cannot distinguish between terminal options on the 328 // shebang line and those on the command line. 329 // On Solaris, all options after the first on the shebang line are 330 // ignored. Similar on AIX. 331 log.println("SKIPPED"); 332 return; 333 } 334 Path base = Files.createDirectories( 335 Paths.get("testTerminalOptionInShebang")); 336 Path bad = base.resolve("bad"); 337 createFile(bad, List.of( 338 "#!" + shebangJavaCmd + " --source " + thisVersion + " -version")); 339 setExecutable(bad); 340 TestResult tr = doExec(bad.toString()); 341 if (!tr.contains("Option -version is not allowed in this context")) 342 error(tr, "Expected output not found"); 343 show(tr); 344 } 345 346 // #!.../java --source N @bad.at (contains -version) 347 @Test 348 void testTerminalOptionInShebangAtFile() throws IOException { 349 starting("testTerminalOptionInShebangAtFile"); 350 if (skipShebangTest || isAIX || isMacOSX) { 351 // On MacOSX, we cannot distinguish between terminal options in a 352 // shebang @-file and those on the command line. 353 // On Solaris, all options after the first on the shebang line are 354 // ignored. Similar on AIX. 355 log.println("SKIPPED"); 356 return; 357 } 358 // Use a short directory name, to avoid line length limitations 359 Path base = Files.createDirectories(Paths.get("testBadAtFile")); 360 Path bad_at = base.resolve("bad.at"); 361 createFile(bad_at, List.of("-version")); 362 Path bad = base.resolve("bad"); 363 createFile(bad, List.of( 364 "#!" + shebangJavaCmd + " --source " + thisVersion + " @" + bad_at)); 365 setExecutable(bad); 366 TestResult tr = doExec(bad.toString()); 367 if (!tr.contains("Option -version in @testBadAtFile/bad.at is " 368 + "not allowed in this context")) 369 error(tr, "Expected output not found"); 370 show(tr); 371 } 372 373 // #!.../java --source N HelloWorld 374 @Test 375 void testMainClassInShebang() throws IOException { 376 starting("testMainClassInShebang"); 377 if (skipShebangTest || isAIX || isMacOSX) { 378 // On MacOSX, we cannot distinguish between a main class on the 379 // shebang line and one on the command line. 380 // On Solaris, all options after the first on the shebang line are 381 // ignored. Similar on AIX. 382 log.println("SKIPPED"); 383 return; 384 } 385 Path base = Files.createDirectories(Paths.get("testMainClassInShebang")); 386 Path bad = base.resolve("bad"); 387 createFile(bad, List.of( 388 "#!" + shebangJavaCmd + " --source " + thisVersion + " HelloWorld")); 389 setExecutable(bad); 390 TestResult tr = doExec(bad.toString()); 391 if (!tr.contains("Cannot specify main class in this context")) 392 error(tr, "Expected output not found"); 393 show(tr); 394 } 395 396 //-------------------------------------------------------------------------- 397 398 private void starting(String label) { 399 System.out.println(); 400 System.out.println("*** Starting: " + label + " (stdout)"); 401 402 System.err.println(); 403 System.err.println("*** Starting: " + label + " (stderr)"); 404 } 405 406 private void show(TestResult tr) { 407 log.println("*** Test Output:"); 408 for (String line: tr.testOutput) { 409 log.println(line); 410 } 411 log.println("*** End Of Test Output:"); 412 } 413 414 private Map<String,String> getLauncherDebugEnv() { 415 return Map.of("_JAVA_LAUNCHER_DEBUG", "1"); 416 } 417 418 private Path getSimpleFile(String name, boolean shebang) throws IOException { 419 Path file = Paths.get(name); 420 if (!Files.exists(file)) { 421 createFile(file, List.of( 422 (shebang ? "#!" + shebangJavaCmd + " --source=" + thisVersion: ""), 423 "public class Simple {", 424 " public static void main(String[] args) {", 425 " System.out.println(java.util.Arrays.toString(args));", 426 " }}")); 427 } 428 return file; 429 } 430 431 private void createFile(Path file, List<String> lines) throws IOException { 432 lines.stream() 433 .filter(line -> line.length() > 128) 434 .forEach(line -> { 435 log.println("*** Warning: long line (" 436 + line.length() 437 + " chars) in file " + file); 438 log.println("*** " + line); 439 }); 440 log.println("*** File: " + file); 441 lines.stream().forEach(log::println); 442 log.println("*** End Of File"); 443 createFile(file.toFile(), lines); 444 } 445 446 private Path setExecutable(Path file) throws IOException { 447 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); 448 perms.add(PosixFilePermission.OWNER_EXECUTE); 449 Files.setPosixFilePermissions(file, perms); 450 return file; 451 } 452 453 private void error(TestResult tr, String message) { 454 show(tr); 455 throw new RuntimeException(message); 456 } 457 }