--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jar/compat/CLICompatibility.java Thu Mar 17 19:04:16 2016 +0000
@@ -0,0 +1,633 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+import java.io.*;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
+
+import jdk.testlibrary.FileUtils;
+import jdk.testlibrary.JDKToolFinder;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import static java.lang.String.format;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertTrue;
+
+/*
+ * @test
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder
+ * @run testng CLICompatibility
+ * @summary Basic test for compatibility of CLI options
+ */
+
+public class CLICompatibility {
+ static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
+ static final Path USER_DIR = Paths.get(System.getProperty("user.dir"));
+
+ final boolean legacyOnly; // for running on older JDK's ( test validation )
+
+ // Resources we know to exist, that can be used for creating jar files.
+ static final String RES1 = "CLICompatibility.class";
+ static final String RES2 = "CLICompatibility$Result.class";
+
+ @BeforeTest
+ public void setupResourcesForJar() throws Exception {
+ // Copy the files that we are going to use for creating/updating test
+ // jar files, so that they can be referred to without '-C dir'
+ Files.copy(TEST_CLASSES.resolve(RES1), USER_DIR.resolve(RES1));
+ Files.copy(TEST_CLASSES.resolve(RES2), USER_DIR.resolve(RES2));
+ }
+
+ static final IOConsumer<InputStream> ASSERT_CONTAINS_RES1 = in -> {
+ try (JarInputStream jin = new JarInputStream(in)) {
+ assertTrue(jarContains(jin, RES1), "Failed to find " + RES1);
+ }
+ };
+ static final IOConsumer<InputStream> ASSERT_CONTAINS_RES2 = in -> {
+ try (JarInputStream jin = new JarInputStream(in)) {
+ assertTrue(jarContains(jin, RES2), "Failed to find " + RES2);
+ }
+ };
+ static final IOConsumer<InputStream> ASSERT_CONTAINS_MAINFEST = in -> {
+ try (JarInputStream jin = new JarInputStream(in)) {
+ assertTrue(jin.getManifest() != null, "No META-INF/MANIFEST.MF");
+ }
+ };
+ static final IOConsumer<InputStream> ASSERT_DOES_NOT_CONTAIN_MAINFEST = in -> {
+ try (JarInputStream jin = new JarInputStream(in)) {
+ assertTrue(jin.getManifest() == null, "Found unexpected META-INF/MANIFEST.MF");
+ }
+ };
+
+ static final FailCheckerWithMessage FAIL_TOO_MANY_MAIN_OPS =
+ new FailCheckerWithMessage("You must specify one of -ctxui options",
+ /* legacy */ "{ctxui}[vfmn0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files");
+
+ // Create
+
+ @Test
+ public void createBadArgs() {
+ final FailCheckerWithMessage FAIL_CREATE_NO_ARGS = new FailCheckerWithMessage(
+ "'c' flag requires manifest or input files to be specified!");
+
+ jar("c")
+ .assertFailure()
+ .resultChecker(FAIL_CREATE_NO_ARGS);
+
+ jar("-c")
+ .assertFailure()
+ .resultChecker(FAIL_CREATE_NO_ARGS);
+
+ if (!legacyOnly)
+ jar("--create")
+ .assertFailure()
+ .resultChecker(FAIL_CREATE_NO_ARGS);
+
+ jar("ct")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ jar("-ct")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ if (!legacyOnly)
+ jar("--create --list")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+ }
+
+ @Test
+ public void createWriteToFile() throws IOException {
+ Path path = Paths.get("createJarFile.jar"); // for creating
+ String jn = path.toString();
+ for (String opts : new String[]{"cf " + jn, "-cf " + jn, "--create --file=" + jn}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jar(opts, RES1)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
+ ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
+ });
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ @Test
+ public void createWriteToStdout() throws IOException {
+ for (String opts : new String[]{"c", "-c", "--create"}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jar(opts, RES1)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
+ ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
+ });
+ }
+ }
+
+ @Test
+ public void createWriteToStdoutNoManifest() throws IOException {
+ for (String opts : new String[]{"cM", "-cM", "--create --no-manifest"} ){
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jar(opts, RES1)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
+ ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
+ });
+ }
+ }
+
+ // Update
+
+ @Test
+ public void updateBadArgs() {
+ final FailCheckerWithMessage FAIL_UPDATE_NO_ARGS = new FailCheckerWithMessage(
+ "'u' flag requires manifest, 'e' flag or input files to be specified!");
+
+ jar("u")
+ .assertFailure()
+ .resultChecker(FAIL_UPDATE_NO_ARGS);
+
+ jar("-u")
+ .assertFailure()
+ .resultChecker(FAIL_UPDATE_NO_ARGS);
+
+ if (!legacyOnly)
+ jar("--update")
+ .assertFailure()
+ .resultChecker(FAIL_UPDATE_NO_ARGS);
+
+ jar("ut")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ jar("-ut")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ if (!legacyOnly)
+ jar("--update --list")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+ }
+
+ @Test
+ public void updateReadFileWriteFile() throws IOException {
+ Path path = Paths.get("updateReadWriteStdout.jar"); // for updating
+ String jn = path.toString();
+
+ for (String opts : new String[]{"uf " + jn, "-uf " + jn, "--update --file=" + jn}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ createJar(path, RES1);
+ jar(opts, RES2)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
+ ASSERT_CONTAINS_RES2.accept(Files.newInputStream(path));
+ ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
+ });
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ @Test
+ public void updateReadStdinWriteStdout() throws IOException {
+ Path path = Paths.get("updateReadStdinWriteStdout.jar");
+
+ for (String opts : new String[]{"u", "-u", "--update"}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ createJar(path, RES1);
+ jarWithStdin(path.toFile(), opts, RES2)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
+ ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
+ ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
+ });
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ @Test
+ public void updateReadStdinWriteStdoutNoManifest() throws IOException {
+ Path path = Paths.get("updateReadStdinWriteStdoutNoManifest.jar");
+
+ for (String opts : new String[]{"uM", "-uM", "--update --no-manifest"} ){
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ createJar(path, RES1);
+ jarWithStdin(path.toFile(), opts, RES2)
+ .assertSuccess()
+ .resultChecker(r -> {
+ ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
+ ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
+ ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
+ });
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ // List
+
+ @Test
+ public void listBadArgs() {
+ jar("te")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ jar("-te")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ if (!legacyOnly)
+ jar("--list --extract")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+ }
+
+ @Test
+ public void listReadFromFileWriteToStdout() throws IOException {
+ Path path = Paths.get("listReadFromFileWriteToStdout.jar"); // for listing
+ createJar(path, RES1);
+ String jn = path.toString();
+
+ for (String opts : new String[]{"tf " + jn, "-tf " + jn, "--list --file " + jn}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jar(opts)
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
+ "Failed, got [" + r.output + "]")
+ );
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ @Test
+ public void listReadFromStdinWriteToStdout() throws IOException {
+ Path path = Paths.get("listReadFromStdinWriteToStdout.jar");
+ createJar(path, RES1);
+
+ for (String opts : new String[]{"t", "-t", "--list"} ){
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jarWithStdin(path.toFile(), opts)
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
+ "Failed, got [" + r.output + "]")
+ );
+ }
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ }
+
+ // Extract
+
+ @Test
+ public void extractBadArgs() {
+ jar("xi")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ jar("-xi")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+
+ if (!legacyOnly)
+ jar("--extract --generate-index")
+ .assertFailure()
+ .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
+ }
+
+ @Test
+ public void extractReadFromStdin() throws IOException {
+ Path path = Paths.get("extract");
+ Path jarPath = path.resolve("extractReadFromStdin.jar"); // for extracting
+ createJar(jarPath, RES1);
+
+ for (String opts : new String[]{"x" ,"-x", "--extract"}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jarWithStdinAndWorkingDir(jarPath.toFile(), path.toFile(), opts)
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(Files.exists(path.resolve(RES1)),
+ "Expected to find:" + path.resolve(RES1))
+ );
+ FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
+ }
+ FileUtils.deleteFileTreeWithRetry(path);
+ }
+
+ @Test
+ public void extractReadFromFile() throws IOException {
+ Path path = Paths.get("extract");
+ String jn = "extractReadFromFile.jar";
+ Path jarPath = path.resolve(jn);
+ createJar(jarPath, RES1);
+
+ for (String opts : new String[]{"xf "+jn ,"-xf "+jn, "--extract --file "+jn}) {
+ if (legacyOnly && opts.startsWith("--"))
+ continue;
+
+ jarWithStdinAndWorkingDir(null, path.toFile(), opts)
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(Files.exists(path.resolve(RES1)),
+ "Expected to find:" + path.resolve(RES1))
+ );
+ FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
+ }
+ FileUtils.deleteFileTreeWithRetry(path);
+ }
+
+ // Basic help
+
+ @Test
+ public void helpBadOptionalArg() {
+ if (legacyOnly)
+ return;
+
+ jar("--help:")
+ .assertFailure();
+
+ jar("--help:blah")
+ .assertFailure();
+ }
+
+ @Test
+ public void help() {
+ if (legacyOnly)
+ return;
+
+ jar("-h")
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.startsWith("Usage: jar [OPTION...] [-C dir] files"),
+ "Failed, got [" + r.output + "]")
+ );
+
+ jar("--help")
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.startsWith("Usage: jar [OPTION...] [-C dir] files"),
+ "Failed, got [" + r.output + "]")
+ );
+
+ jar("--help:compat")
+ .assertSuccess()
+ .resultChecker(r ->
+ assertTrue(r.output.startsWith("Compatibility Interface:"),
+ "Failed, got [" + r.output + "]")
+ );
+ }
+
+ // -- Infrastructure
+
+ static boolean jarContains(JarInputStream jis, String entryName)
+ throws IOException
+ {
+ JarEntry e;
+ boolean found = false;
+ while((e = jis.getNextJarEntry()) != null) {
+ if (e.getName().equals(entryName))
+ return true;
+ }
+ return false;
+ }
+
+ /* Creates a simple jar with entries of size 0, good enough for testing */
+ static void createJar(Path path, String... entries) throws IOException {
+ FileUtils.deleteFileIfExistsWithRetry(path);
+ Path parent = path.getParent();
+ if (parent != null)
+ Files.createDirectories(parent);
+ try (OutputStream out = Files.newOutputStream(path);
+ JarOutputStream jos = new JarOutputStream(out)) {
+ JarEntry je = new JarEntry("META-INF/MANIFEST.MF");
+ jos.putNextEntry(je);
+ jos.closeEntry();
+
+ for (String entry : entries) {
+ je = new JarEntry(entry);
+ jos.putNextEntry(je);
+ jos.closeEntry();
+ }
+ }
+ }
+
+ static class FailCheckerWithMessage implements Consumer<Result> {
+ final String[] messages;
+ FailCheckerWithMessage(String... m) {
+ messages = m;
+ }
+ @Override
+ public void accept(Result r) {
+ //out.printf("%s%n", r.output);
+ boolean found = false;
+ for (String m : messages) {
+ if (r.output.contains(m)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found,
+ "Excepted out to contain one of: " + Arrays.asList(messages)
+ + " but got: " + r.output);
+ }
+ }
+
+ static Result jar(String... args) {
+ return jarWithStdinAndWorkingDir(null, null, args);
+ }
+
+ static Result jarWithStdin(File stdinSource, String... args) {
+ return jarWithStdinAndWorkingDir(stdinSource, null, args);
+ }
+
+ static Result jarWithStdinAndWorkingDir(File stdinFrom,
+ File workingDir,
+ String... args) {
+ String jar = getJDKTool("jar");
+ List<String> commands = new ArrayList<>();
+ commands.add(jar);
+ Stream.of(args).map(s -> s.split(" "))
+ .flatMap(Arrays::stream)
+ .forEach(x -> commands.add(x));
+ ProcessBuilder p = new ProcessBuilder(commands);
+ if (stdinFrom != null)
+ p.redirectInput(stdinFrom);
+ if (workingDir != null)
+ p.directory(workingDir);
+ return run(p);
+ }
+
+ static Result run(ProcessBuilder pb) {
+ Process p;
+ byte[] stdout, stderr;
+ out.printf("Running: %s%n", pb.command());
+ try {
+ p = pb.start();
+ } catch (IOException e) {
+ throw new RuntimeException(
+ format("Couldn't start process '%s'", pb.command()), e);
+ }
+
+ String output;
+ try {
+ stdout = readAllBytes(p.getInputStream());
+ stderr = readAllBytes(p.getErrorStream());
+
+ output = toString(stdout, stderr);
+ } catch (IOException e) {
+ throw new RuntimeException(
+ format("Couldn't read process output '%s'", pb.command()), e);
+ }
+
+ try {
+ p.waitFor();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ format("Process hasn't finished '%s'", pb.command()), e);
+ }
+ return new Result(p.exitValue(), stdout, stderr, output);
+ }
+
+ static final Path JAVA_HOME = Paths.get(System.getProperty("java.home"));
+
+ static String getJDKTool(String name) {
+ try {
+ return JDKToolFinder.getJDKTool(name);
+ } catch (Exception x) {
+ Path j = JAVA_HOME.resolve("bin").resolve(name);
+ if (Files.exists(j))
+ return j.toString();
+ j = JAVA_HOME.resolve("..").resolve("bin").resolve(name);
+ if (Files.exists(j))
+ return j.toString();
+ throw new RuntimeException(x);
+ }
+ }
+
+ static String toString(byte[] ba1, byte[] ba2) {
+ return (new String(ba1, UTF_8)).concat(new String(ba2, UTF_8));
+ }
+
+ static class Result {
+ final int exitValue;
+ final byte[] stdout;
+ final byte[] stderr;
+ final String output;
+
+ private Result(int exitValue, byte[] stdout, byte[] stderr, String output) {
+ this.exitValue = exitValue;
+ this.stdout = stdout;
+ this.stderr = stderr;
+ this.output = output;
+ }
+
+ InputStream stdoutAsStream() { return new ByteArrayInputStream(stdout); }
+
+ Result assertSuccess() { assertTrue(exitValue == 0, output); return this; }
+ Result assertFailure() { assertTrue(exitValue != 0, output); return this; }
+
+ Result resultChecker(IOConsumer<Result> r) {
+ try { r.accept(this); return this; }
+ catch (IOException x) { throw new UncheckedIOException(x); }
+ }
+
+ Result resultChecker(FailCheckerWithMessage c) { c.accept(this); return this; }
+ }
+
+ interface IOConsumer<T> { void accept(T t) throws IOException ; }
+
+ // readAllBytes implementation so the test can be run pre 1.9 ( legacyOnly )
+ static byte[] readAllBytes(InputStream is) throws IOException {
+ byte[] buf = new byte[8192];
+ int capacity = buf.length;
+ int nread = 0;
+ int n;
+ for (;;) {
+ // read to EOF which may read more or less than initial buffer size
+ while ((n = is.read(buf, nread, capacity - nread)) > 0)
+ nread += n;
+
+ // if the last call to read returned -1, then we're done
+ if (n < 0)
+ break;
+
+ // need to allocate a larger buffer
+ capacity = capacity << 1;
+
+ buf = Arrays.copyOf(buf, capacity);
+ }
+ return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
+ }
+
+ // Standalone entry point for running with, possibly older, JDKs.
+ public static void main(String[] args) throws Throwable {
+ boolean legacyOnly = false;
+ if (args.length != 0 && args[0].equals("legacyOnly"))
+ legacyOnly = true;
+
+ CLICompatibility test = new CLICompatibility(legacyOnly);
+ for (Method m : CLICompatibility.class.getDeclaredMethods()) {
+ if (m.getAnnotation(Test.class) != null) {
+ System.out.println("Invoking " + m.getName());
+ m.invoke(test);
+ }
+ }
+ }
+ CLICompatibility(boolean legacyOnly) { this.legacyOnly = legacyOnly; }
+ CLICompatibility() { this.legacyOnly = false; }
+}