Quelle CLICompatibility.java
Sprache: JAVA
/*
* Copyright ( c ) 2015 , 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 .
*/
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.test.lib.util.FileUtils;
import jdk.test.lib.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 .assertFalse;
import static org.testng.Assert .assertTrue;
/*
* @ test
* @ bug 8170952
* @ library / lib / testlibrary / test / lib
* @ build jdk . test . lib . Platform
* jdk . test . lib . util . FileUtils
* jdk . test . lib . 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" ));
static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts" , "" );
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 may not specify more than one '-cuxtid' 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("tx" )
.assertFailure()
.resultChecker(FAIL_TOO_MANY_MAIN_OPS);
jar("-tx" )
.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(new FailCheckerWithMessage(
"option --generate-index requires an argument" ));
jar("--extract --generate-index=foo" )
.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...] [ [--release VERSION] [-C dir] files]" ),
"Failed, got [" + r.output + "]" )
);
jar("--help" )
.assertSuccess()
.resultChecker(r -> {
assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]" ),
"Failed, got [" + r.output + "]" );
assertFalse(r.output.contains("--do-not-resolve-by-default" ));
assertFalse(r.output.contains("--warn-if-resolved" ));
});
jar("--help:compat" )
.assertSuccess()
.resultChecker(r ->
assertTrue(r.output.startsWith("Compatibility Interface:" ),
"Failed, got [" + r.output + "]" )
);
jar("--help-extra" )
.assertSuccess()
.resultChecker(r -> {
assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]" ),
"Failed, got [" + r.output + "]" );
assertTrue(r.output.contains("--do-not-resolve-by-default" ));
assertTrue(r.output.contains("--warn-if-resolved" ));
});
}
// -- 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);
if (!TOOL_VM_OPTIONS.isEmpty()) {
commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+" , -1 )));
}
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 ; }
}
Messung V0.5 in Prozent C=97 H=79 G=88
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-19)
¤
*© Formatika GbR, Deutschland
2026-07-11