| /* |
| * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /** |
| * @test |
| * @bug 8192920 8204588 |
| * @summary Test source mode |
| * @modules jdk.compiler jdk.jlink |
| * @run main SourceMode |
| */ |
| |
| |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.attribute.PosixFilePermission; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.spi.ToolProvider; |
| |
| public class SourceMode extends TestHelper { |
| |
| public static void main(String... args) throws Exception { |
| new SourceMode().run(args); |
| } |
| |
| // To reduce the chance of creating shebang lines that are too long, |
| // use a shorter path for a java command if the standard path is too long. |
| private final Path shebangJavaCmd; |
| |
| // Whether or not to automatically skip the shebang tests |
| private final boolean skipShebangTest; |
| |
| private final PrintStream log; |
| |
| private static final String thisVersion = System.getProperty("java.specification.version"); |
| |
| SourceMode() throws Exception { |
| log = System.err; |
| |
| if (isWindows) { |
| // Skip shebang tests on Windows, because that requires Cygwin. |
| skipShebangTest = true; |
| shebangJavaCmd = null; |
| } else { |
| // Try to ensure the path to the Java launcher is reasonably short, |
| // to work around the mostly undocumented limit of 120 characters |
| // for a shebang line. |
| // The value of 120 is the typical kernel compile-time buffer limit. |
| // The following limit of 80 allows room for arguments to be placed |
| // after the path to the launcher on the shebang line. |
| Path cmd = Paths.get(javaCmd); |
| if (cmd.toString().length() < 80) { |
| shebangJavaCmd = cmd; |
| } else { |
| // Create a small image in the current directory, such that |
| // the path for the launcher is just "tmpJDK/bin/java". |
| Path tmpJDK = Paths.get("tmpJDK"); |
| ToolProvider jlink = ToolProvider.findFirst("jlink") |
| .orElseThrow(() -> new Exception("cannot find jlink")); |
| jlink.run(System.out, System.err, |
| "--add-modules", "jdk.compiler,jdk.zipfs", "--output", tmpJDK.toString()); |
| shebangJavaCmd = tmpJDK.resolve("bin").resolve("java"); |
| } |
| log.println("Using java command: " + shebangJavaCmd); |
| skipShebangTest = false; |
| } |
| } |
| |
| // java Simple.java 1 2 3 |
| @Test |
| void testSimpleJava() throws IOException { |
| starting("testSimpleJava"); |
| Path file = getSimpleFile("Simple.java", false); |
| TestResult tr = doExec(javaCmd, file.toString(), "1", "2", "3"); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("[1, 2, 3]")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java --source N simple 1 2 3 |
| @Test |
| void testSimple() throws IOException { |
| starting("testSimple"); |
| Path file = getSimpleFile("simple", false); |
| TestResult tr = doExec(javaCmd, "--source", thisVersion, file.toString(), "1", "2", "3"); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("[1, 2, 3]")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // execSimple 1 2 3 |
| @Test |
| void testExecSimple() throws IOException { |
| starting("testExecSimple"); |
| if (skipShebangTest) { |
| log.println("SKIPPED"); |
| return; |
| } |
| Path file = setExecutable(getSimpleFile("execSimple", true)); |
| TestResult tr = doExec(file.toAbsolutePath().toString(), "1", "2", "3"); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("[1, 2, 3]")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java @simpleJava.at (contains Simple.java 1 2 3) |
| @Test |
| void testSimpleJavaAtFile() throws IOException { |
| starting("testSimpleJavaAtFile"); |
| Path file = getSimpleFile("Simple.java", false); |
| Path atFile = Paths.get("simpleJava.at"); |
| createFile(atFile, List.of(file + " 1 2 3")); |
| TestResult tr = doExec(javaCmd, "@" + atFile); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("[1, 2, 3]")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java @simple.at (contains --source N simple 1 2 3) |
| @Test |
| void testSimpleAtFile() throws IOException { |
| starting("testSimpleAtFile"); |
| Path file = getSimpleFile("simple", false); |
| Path atFile = Paths.get("simple.at"); |
| createFile(atFile, List.of("--source " + thisVersion + " " + file + " 1 2 3")); |
| TestResult tr = doExec(javaCmd, "@" + atFile); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("[1, 2, 3]")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java -cp classes Main.java 1 2 3 |
| @Test |
| void testClasspath() throws IOException { |
| starting("testClasspath"); |
| Path base = Files.createDirectories(Paths.get("testClasspath")); |
| Path otherJava = base.resolve("Other.java"); |
| createFile(otherJava, List.of( |
| "public class Other {", |
| " public static String join(String[] args) {", |
| " return String.join(\"-\", args);", |
| " }", |
| "}" |
| )); |
| Path classes = Files.createDirectories(base.resolve("classes")); |
| Path mainJava = base.resolve("Main.java"); |
| createFile(mainJava, List.of( |
| "class Main {", |
| " public static void main(String[] args) {", |
| " System.out.println(Other.join(args));", |
| " }}" |
| )); |
| compile("-d", classes.toString(), otherJava.toString()); |
| TestResult tr = doExec(javaCmd, "-cp", classes.toString(), |
| mainJava.toString(), "1", "2", "3"); |
| if (!tr.isOK()) |
| error(tr, "Bad exit code: " + tr.exitValue); |
| if (!tr.contains("1-2-3")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java --add-exports=... Export.java --help |
| @Test |
| void testAddExports() throws IOException { |
| starting("testAddExports"); |
| Path exportJava = Paths.get("Export.java"); |
| createFile(exportJava, List.of( |
| "public class Export {", |
| " public static void main(String[] args) {", |
| " new com.sun.tools.javac.main.Main(\"demo\").compile(args);", |
| " }", |
| "}" |
| )); |
| // verify access fails without --add-exports |
| TestResult tr1 = doExec(javaCmd, exportJava.toString(), "--help"); |
| if (tr1.isOK()) |
| error(tr1, "Compilation succeeded unexpectedly"); |
| show(tr1); |
| // verify access succeeds with --add-exports |
| TestResult tr2 = doExec(javaCmd, |
| "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", |
| exportJava.toString(), "--help"); |
| if (!tr2.isOK()) |
| error(tr2, "Bad exit code: " + tr2.exitValue); |
| if (!(tr2.contains("demo") && tr2.contains("Usage"))) |
| error(tr2, "Expected output not found"); |
| show(tr2); |
| } |
| |
| // java -cp ... HelloWorld.java (for a class "java" in package "HelloWorld") |
| @Test |
| void testClassNamedJava() throws IOException { |
| starting("testClassNamedJava"); |
| Path base = Files.createDirectories(Paths.get("testClassNamedJava")); |
| Path src = Files.createDirectories(base.resolve("src")); |
| Path srcfile = src.resolve("java.java"); |
| createFile(srcfile, List.of( |
| "package HelloWorld;", |
| "class java {", |
| " public static void main(String... args) {", |
| " System.out.println(HelloWorld.java.class.getName());", |
| " }", |
| "}" |
| )); |
| Path classes = base.resolve("classes"); |
| compile("-d", classes.toString(), srcfile.toString()); |
| TestResult tr = |
| doExec(javaCmd, "-cp", classes.toString(), "HelloWorld.java"); |
| if (!tr.isOK()) |
| error(tr, "Command failed"); |
| if (!tr.contains("HelloWorld.java")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java --source |
| @Test |
| void testSourceNoArg() throws IOException { |
| starting("testSourceNoArg"); |
| TestResult tr = doExec(javaCmd, "--source"); |
| if (tr.isOK()) |
| error(tr, "Command succeeded unexpectedly"); |
| if (!tr.contains("--source requires source version")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java --source N -jar simple.jar |
| @Test |
| void testSourceJarConflict() throws IOException { |
| starting("testSourceJarConflict"); |
| Path base = Files.createDirectories(Paths.get("testSourceJarConflict")); |
| Path file = getSimpleFile("Simple.java", false); |
| Path classes = Files.createDirectories(base.resolve("classes")); |
| compile("-d", classes.toString(), file.toString()); |
| Path simpleJar = base.resolve("simple.jar"); |
| createJar("cf", simpleJar.toString(), "-C", classes.toString(), "."); |
| TestResult tr = |
| doExec(javaCmd, "--source", thisVersion, "-jar", simpleJar.toString()); |
| if (tr.isOK()) |
| error(tr, "Command succeeded unexpectedly"); |
| if (!tr.contains("Option -jar is not allowed with --source")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // java --source N -m jdk.compiler |
| @Test |
| void testSourceModuleConflict() throws IOException { |
| starting("testSourceModuleConflict"); |
| TestResult tr = doExec(javaCmd, "--source", thisVersion, "-m", "jdk.compiler"); |
| if (tr.isOK()) |
| error(tr, "Command succeeded unexpectedly"); |
| if (!tr.contains("Option -m is not allowed with --source")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // #!.../java --source N -version |
| @Test |
| void testTerminalOptionInShebang() throws IOException { |
| starting("testTerminalOptionInShebang"); |
| if (skipShebangTest || isAIX || isMacOSX || isSolaris) { |
| // On MacOSX, we cannot distinguish between terminal options on the |
| // shebang line and those on the command line. |
| // On Solaris, all options after the first on the shebang line are |
| // ignored. Similar on AIX. |
| log.println("SKIPPED"); |
| return; |
| } |
| Path base = Files.createDirectories( |
| Paths.get("testTerminalOptionInShebang")); |
| Path bad = base.resolve("bad"); |
| createFile(bad, List.of( |
| "#!" + shebangJavaCmd + " --source " + thisVersion + " -version")); |
| setExecutable(bad); |
| TestResult tr = doExec(bad.toString()); |
| if (!tr.contains("Option -version is not allowed in this context")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // #!.../java --source N @bad.at (contains -version) |
| @Test |
| void testTerminalOptionInShebangAtFile() throws IOException { |
| starting("testTerminalOptionInShebangAtFile"); |
| if (skipShebangTest || isAIX || isMacOSX || isSolaris) { |
| // On MacOSX, we cannot distinguish between terminal options in a |
| // shebang @-file and those on the command line. |
| // On Solaris, all options after the first on the shebang line are |
| // ignored. Similar on AIX. |
| log.println("SKIPPED"); |
| return; |
| } |
| // Use a short directory name, to avoid line length limitations |
| Path base = Files.createDirectories(Paths.get("testBadAtFile")); |
| Path bad_at = base.resolve("bad.at"); |
| createFile(bad_at, List.of("-version")); |
| Path bad = base.resolve("bad"); |
| createFile(bad, List.of( |
| "#!" + shebangJavaCmd + " --source " + thisVersion + " @" + bad_at)); |
| setExecutable(bad); |
| TestResult tr = doExec(bad.toString()); |
| if (!tr.contains("Option -version in @testBadAtFile/bad.at is " |
| + "not allowed in this context")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| // #!.../java --source N HelloWorld |
| @Test |
| void testMainClassInShebang() throws IOException { |
| starting("testMainClassInShebang"); |
| if (skipShebangTest || isAIX || isMacOSX || isSolaris) { |
| // On MacOSX, we cannot distinguish between a main class on the |
| // shebang line and one on the command line. |
| // On Solaris, all options after the first on the shebang line are |
| // ignored. Similar on AIX. |
| log.println("SKIPPED"); |
| return; |
| } |
| Path base = Files.createDirectories(Paths.get("testMainClassInShebang")); |
| Path bad = base.resolve("bad"); |
| createFile(bad, List.of( |
| "#!" + shebangJavaCmd + " --source " + thisVersion + " HelloWorld")); |
| setExecutable(bad); |
| TestResult tr = doExec(bad.toString()); |
| if (!tr.contains("Cannot specify main class in this context")) |
| error(tr, "Expected output not found"); |
| show(tr); |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| private void starting(String label) { |
| System.out.println(); |
| System.out.println("*** Starting: " + label + " (stdout)"); |
| |
| System.err.println(); |
| System.err.println("*** Starting: " + label + " (stderr)"); |
| } |
| |
| private void show(TestResult tr) { |
| log.println("*** Test Output:"); |
| for (String line: tr.testOutput) { |
| log.println(line); |
| } |
| log.println("*** End Of Test Output:"); |
| } |
| |
| private Map<String,String> getLauncherDebugEnv() { |
| return Map.of("_JAVA_LAUNCHER_DEBUG", "1"); |
| } |
| |
| private Path getSimpleFile(String name, boolean shebang) throws IOException { |
| Path file = Paths.get(name); |
| if (!Files.exists(file)) { |
| createFile(file, List.of( |
| (shebang ? "#!" + shebangJavaCmd + " --source=" + thisVersion: ""), |
| "public class Simple {", |
| " public static void main(String[] args) {", |
| " System.out.println(java.util.Arrays.toString(args));", |
| " }}")); |
| } |
| return file; |
| } |
| |
| private void createFile(Path file, List<String> lines) throws IOException { |
| lines.stream() |
| .filter(line -> line.length() > 128) |
| .forEach(line -> { |
| log.println("*** Warning: long line (" |
| + line.length() |
| + " chars) in file " + file); |
| log.println("*** " + line); |
| }); |
| log.println("*** File: " + file); |
| lines.stream().forEach(log::println); |
| log.println("*** End Of File"); |
| createFile(file.toFile(), lines); |
| } |
| |
| private Path setExecutable(Path file) throws IOException { |
| Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); |
| perms.add(PosixFilePermission.OWNER_EXECUTE); |
| Files.setPosixFilePermissions(file, perms); |
| return file; |
| } |
| |
| private void error(TestResult tr, String message) { |
| show(tr); |
| throw new RuntimeException(message); |
| } |
| } |