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 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 255 @Test 256 void testSourceNoArg() throws IOException { 257 starting("testSourceNoArg"); 258 TestResult tr = doExec(javaCmd, "--source"); 259 if (tr.isOK()) 260 error(tr, "Command succeeded unexpectedly"); 261 if (!tr.contains("--source requires source version")) 262 error(tr, "Expected output not found"); 263 show(tr); 264 } 265 266 // java --source N -jar simple.jar 267 @Test 268 void testSourceJarConflict() throws IOException { 269 starting("testSourceJarConflict"); 270 Path base = Files.createDirectories(Paths.get("testSourceJarConflict")); 271 Path file = getSimpleFile("Simple.java", false); 272 Path classes = Files.createDirectories(base.resolve("classes")); 273 compile("-d", classes.toString(), file.toString()); 274 Path simpleJar = base.resolve("simple.jar"); 275 createJar("cf", simpleJar.toString(), "-C", classes.toString(), "."); 276 TestResult tr = 277 doExec(javaCmd, "--source", thisVersion, "-jar", simpleJar.toString()); 278 if (tr.isOK()) 279 error(tr, "Command succeeded unexpectedly"); 280 if (!tr.contains("Option -jar is not allowed with --source")) 281 error(tr, "Expected output not found"); 282 show(tr); 283 } 284 285 // java --source N -m jdk.compiler 286 @Test 287 void testSourceModuleConflict() throws IOException { 288 starting("testSourceModuleConflict"); 289 TestResult tr = doExec(javaCmd, "--source", thisVersion, "-m", "jdk.compiler"); 290 if (tr.isOK()) 291 error(tr, "Command succeeded unexpectedly"); 292 if (!tr.contains("Option -m is not allowed with --source")) 293 error(tr, "Expected output not found"); 294 show(tr); 295 } 296 297 // #!.../java --source N -version 298 @Test 299 void testTerminalOptionInShebang() throws IOException { 300 starting("testTerminalOptionInShebang"); 301 if (skipShebangTest || isAIX || isMacOSX || isSolaris) { 302 // On MacOSX, we cannot distinguish between terminal options on the 303 // shebang line and those on the command line. 304 // On Solaris, all options after the first on the shebang line are 305 // ignored. Similar on AIX. 306 log.println("SKIPPED"); 307 return; 308 } 309 Path base = Files.createDirectories( 310 Paths.get("testTerminalOptionInShebang")); 311 Path bad = base.resolve("bad"); 312 createFile(bad, List.of( 313 "#!" + shebangJavaCmd + " --source " + thisVersion + " -version")); 314 setExecutable(bad); 315 TestResult tr = doExec(bad.toString()); 316 if (!tr.contains("Option -version is not allowed in this context")) 317 error(tr, "Expected output not found"); 318 show(tr); 319 } 320 321 // #!.../java --source N @bad.at (contains -version) 322 @Test 323 void testTerminalOptionInShebangAtFile() throws IOException { 324 starting("testTerminalOptionInShebangAtFile"); 325 if (skipShebangTest || isAIX || isMacOSX || isSolaris) { 326 // On MacOSX, we cannot distinguish between terminal options in a 327 // shebang @-file and those on the command line. 328 // On Solaris, all options after the first on the shebang line are 329 // ignored. Similar on AIX. 330 log.println("SKIPPED"); 331 return; 332 } 333 // Use a short directory name, to avoid line length limitations 334 Path base = Files.createDirectories(Paths.get("testBadAtFile")); 335 Path bad_at = base.resolve("bad.at"); 336 createFile(bad_at, List.of("-version")); 337 Path bad = base.resolve("bad"); 338 createFile(bad, List.of( 339 "#!" + shebangJavaCmd + " --source " + thisVersion + " @" + bad_at)); 340 setExecutable(bad); 341 TestResult tr = doExec(bad.toString()); 342 if (!tr.contains("Option -version in @testBadAtFile/bad.at is " 343 + "not allowed in this context")) 344 error(tr, "Expected output not found"); 345 show(tr); 346 } 347 348 // #!.../java --source N HelloWorld 349 @Test 350 void testMainClassInShebang() throws IOException { 351 starting("testMainClassInShebang"); 352 if (skipShebangTest || isAIX || isMacOSX || isSolaris) { 353 // On MacOSX, we cannot distinguish between a main class on the 354 // shebang line and one on the command line. 355 // On Solaris, all options after the first on the shebang line are 356 // ignored. Similar on AIX. 357 log.println("SKIPPED"); 358 return; 359 } 360 Path base = Files.createDirectories(Paths.get("testMainClassInShebang")); 361 Path bad = base.resolve("bad"); 362 createFile(bad, List.of( 363 "#!" + shebangJavaCmd + " --source " + thisVersion + " HelloWorld")); 364 setExecutable(bad); 365 TestResult tr = doExec(bad.toString()); 366 if (!tr.contains("Cannot specify main class in this context")) 367 error(tr, "Expected output not found"); 368 show(tr); 369 } 370 371 //-------------------------------------------------------------------------- 372 373 private void starting(String label) { 374 System.out.println(); 375 System.out.println("*** Starting: " + label + " (stdout)"); 376 377 System.err.println(); 378 System.err.println("*** Starting: " + label + " (stderr)"); 379 } 380 381 private void show(TestResult tr) { 382 log.println("*** Test Output:"); 383 for (String line: tr.testOutput) { 384 log.println(line); 385 } 386 log.println("*** End Of Test Output:"); 387 } 388 389 private Map<String,String> getLauncherDebugEnv() { 390 return Map.of("_JAVA_LAUNCHER_DEBUG", "1"); 391 } 392 393 private Path getSimpleFile(String name, boolean shebang) throws IOException { 394 Path file = Paths.get(name); 395 if (!Files.exists(file)) { 396 createFile(file, List.of( 397 (shebang ? "#!" + shebangJavaCmd + " --source=" + thisVersion: ""), 398 "public class Simple {", 399 " public static void main(String[] args) {", 400 " System.out.println(java.util.Arrays.toString(args));", 401 " }}")); 402 } 403 return file; 404 } 405 406 private void createFile(Path file, List<String> lines) throws IOException { 407 lines.stream() 408 .filter(line -> line.length() > 128) 409 .forEach(line -> { 410 log.println("*** Warning: long line (" 411 + line.length() 412 + " chars) in file " + file); 413 log.println("*** " + line); 414 }); 415 log.println("*** File: " + file); 416 lines.stream().forEach(log::println); 417 log.println("*** End Of File"); 418 createFile(file.toFile(), lines); 419 } 420 421 private Path setExecutable(Path file) throws IOException { 422 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); 423 perms.add(PosixFilePermission.OWNER_EXECUTE); 424 Files.setPosixFilePermissions(file, perms); 425 return file; 426 } 427 428 private void error(TestResult tr, String message) { 429 show(tr); 430 throw new RuntimeException(message); 431 } 432 }