8344322: Improve capabilities of jpackage test lib to validate error output of jpackage

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2024-11-19 13:53:45 +00:00
parent 41436bb0e8
commit 0714114fe3
5 changed files with 218 additions and 38 deletions

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2024, 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.
*/
package jdk.jpackage.test;
import java.util.List;
import java.util.function.BiFunction;
public final class CannedFormattedString {
CannedFormattedString(BiFunction<String, Object[], String> formatter,
String key, Object[] args) {
this.formatter = formatter;
this.key = key;
this.args = args;
}
public String getValue() {
return formatter.apply(key, args);
}
@Override
public String toString() {
if (args.length == 0) {
return String.format("%s", key);
} else {
return String.format("%s+%s", key, List.of(args));
}
}
private final BiFunction<String, Object[], String> formatter;
private final String key;
private final Object[] args;
}

View File

@ -78,6 +78,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
prerequisiteActions = new Actions(cmd.prerequisiteActions);
verifyActions = new Actions(cmd.verifyActions);
appLayoutAsserts = cmd.appLayoutAsserts;
outputValidator = cmd.outputValidator;
executeInDirectory = cmd.executeInDirectory;
}
@ -739,6 +740,24 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
return this;
}
public JPackageCommand validateOutput(TKit.TextStreamVerifier validator) {
return JPackageCommand.this.validateOutput(validator::apply);
}
public JPackageCommand validateOutput(Consumer<Stream<String>> validator) {
if (validator != null) {
saveConsoleOutput(true);
outputValidator = validator;
} else {
outputValidator = null;
}
return this;
}
public JPackageCommand validateOutput(CannedFormattedString str) {
return JPackageCommand.this.validateOutput(TKit.assertTextStream(str.getValue()));
}
public boolean isWithToolProvider() {
return Optional.ofNullable(withToolProvider).orElse(
defaultWithToolProvider);
@ -817,6 +836,10 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
.createExecutor()
.execute(expectedExitCode);
if (outputValidator != null) {
outputValidator.accept(result.getOutput().stream());
}
if (result.exitCode == 0) {
executeVerifyActions();
}
@ -1187,6 +1210,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
private final Actions verifyActions;
private Path executeInDirectory;
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
private Consumer<Stream<String>> outputValidator;
private static boolean defaultWithToolProvider;
private static final Map<String, PackageType> PACKAGE_TYPES = Functional.identity(

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2024, 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.
*/
package jdk.jpackage.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
public enum JPackageStringBundle {
MAIN("jdk.jpackage.internal.I18N"),
;
JPackageStringBundle(String i18nClassName) {
try {
i18nClass = Class.forName(i18nClassName);
i18nClass_getString = i18nClass.getDeclaredMethod("getString", String.class);
i18nClass_getString.setAccessible(true);
} catch (ClassNotFoundException|NoSuchMethodException ex) {
throw Functional.rethrowUnchecked(ex);
}
}
/**
* Return a string value of the given key from jpackage resources.
*/
private String getString(String key) {
try {
return (String)i18nClass_getString.invoke(i18nClass, key);
} catch (IllegalAccessException|InvocationTargetException ex) {
throw Functional.rethrowUnchecked(ex);
}
}
private String getFormattedString(String key, Object[] args) {
return MessageFormat.format(getString(key), args);
}
public CannedFormattedString cannedFormattedString(String key, String ... args) {
return new CannedFormattedString(this::getFormattedString, key, args);
}
private final Class<?> i18nClass;
private final Method i18nClass_getString;
}

View File

@ -946,6 +946,16 @@ public final class TKit {
return this;
}
public TextStreamVerifier andThen(Consumer<? super Stream<String>> anotherVerifier) {
this.anotherVerifier = anotherVerifier;
return this;
}
public TextStreamVerifier andThen(TextStreamVerifier anotherVerifier) {
this.anotherVerifier = anotherVerifier::apply;
return this;
}
public TextStreamVerifier orElseThrow(RuntimeException v) {
return orElseThrow(() -> v);
}
@ -956,9 +966,22 @@ public final class TKit {
}
public void apply(Stream<String> lines) {
String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse(
null);
String labelStr = Optional.ofNullable(label).orElse("output");
final String matchedStr;
lines = lines.dropWhile(line -> !predicate.test(line, value));
if (anotherVerifier == null) {
matchedStr = lines.findFirst().orElse(null);
} else {
var tail = lines.toList();
if (tail.isEmpty()) {
matchedStr = null;
} else {
matchedStr = tail.get(0);
}
lines = tail.stream().skip(1);
}
final String labelStr = Optional.ofNullable(label).orElse("output");
if (negate) {
String msg = String.format(
"Check %s doesn't contain [%s] string", labelStr, value);
@ -982,12 +1005,17 @@ public final class TKit {
}
}
}
if (anotherVerifier != null) {
anotherVerifier.accept(lines);
}
}
private BiPredicate<String, String> predicate;
private String label;
private boolean negate;
private Supplier<RuntimeException> createException;
private Consumer<? super Stream<String>> anotherVerifier;
private final String value;
}

View File

@ -24,9 +24,13 @@
import java.util.Collection;
import java.util.List;
import jdk.jpackage.test.Annotations.Parameters;
import java.util.Optional;
import java.util.stream.Stream;
import jdk.jpackage.test.Annotations.ParameterSupplier;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.TKit;
/*
@ -53,81 +57,85 @@ import jdk.jpackage.test.TKit;
public final class ErrorTest {
private final String expectedError;
private final JPackageCommand cmd;
@Parameters
public static Collection input() {
return List.of(new Object[][]{
// non-existent arg
{"Hello",
new String[]{"--no-such-argument"},
null,
"Invalid Option: [--no-such-argument]"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidOption", "--no-such-argument")},
// no main jar
{"Hello",
null,
new String[]{"--main-jar"},
"--main-jar or --module"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_NoEntryPoint")},
// no main-class
{"Hello",
null,
new String[]{"--main-class"},
"main class was not specified"},
JPackageStringBundle.MAIN.cannedFormattedString("error.no-main-class-with-main-jar", "hello.jar"),
JPackageStringBundle.MAIN.cannedFormattedString("error.no-main-class-with-main-jar.advice", "hello.jar")},
// non-existent main jar
{"Hello",
new String[]{"--main-jar", "non-existent.jar"},
null,
"main jar does not exist"},
JPackageStringBundle.MAIN.cannedFormattedString("error.main-jar-does-not-exist", "non-existent.jar")},
// non-existent runtime
{"Hello",
new String[]{"--runtime-image", "non-existent.runtime"},
null,
"does not exist"},
JPackageStringBundle.MAIN.cannedFormattedString("message.runtime-image-dir-does-not-exist", "runtime-image", "non-existent.runtime")},
// non-existent resource-dir
{"Hello",
new String[]{"--resource-dir", "non-existent.dir"},
null,
"does not exist"},
JPackageStringBundle.MAIN.cannedFormattedString("message.resource-dir-does-not-exist", "resource-dir", "non-existent.dir")},
// invalid type
{"Hello",
new String[]{"--type", "invalid-type"},
null,
"Invalid or unsupported type: [invalid-type]"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidInstallerType", "invalid-type")},
// no --input
{"Hello",
null,
new String[]{"--input"},
"Missing argument: --input"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_MissingArgument", "--input")},
// no --module-path
{"com.other/com.other.Hello",
null,
new String[]{"--module-path"},
"Missing argument: --runtime-image or --module-path"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_MissingArgument", "--runtime-image or --module-path")},
});
}
public ErrorTest(String javaAppDesc, String[] jpackageArgs,
String[] removeArgs,
String expectedError) {
this.expectedError = expectedError;
cmd = JPackageCommand.helloAppImage(javaAppDesc)
.saveConsoleOutput(true).dumpOutput(true);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
} if (removeArgs != null) {
for (String arg : removeArgs) {
cmd.removeArgumentWithValue(arg);
}
}
}
@Test
public void test() {
List<String> output = cmd.execute(1).getOutput();
TKit.assertNotNull(output, "output is null");
TKit.assertTextStream(expectedError).apply(output.stream());
}
@ParameterSupplier("input")
public static void test(String javaAppDesc, String[] jpackageArgs,
String[] removeArgs, CannedFormattedString... expectedErrors) {
// Init default jpackage test command line.
var cmd = JPackageCommand.helloAppImage(javaAppDesc)
// Disable default logic adding `--verbose` option
// to jpackage command line.
// It will affect jpackage error messages if the command line is malformed.
.ignoreDefaultVerbose(true)
// Ignore external runtime as it will interfer
// with jpackage arguments in this test.
.ignoreDefaultRuntime(true);
// Add arguments if requested.
Optional.ofNullable(jpackageArgs).ifPresent(cmd::addArguments);
// Remove arguments if requested.
Optional.ofNullable(removeArgs).map(List::of).ifPresent(
args -> args.forEach(cmd::removeArgumentWithValue));
// Configure jpackage output verifier to look up the list of provided
// errors in the order they specified.
cmd.validateOutput(Stream.of(expectedErrors)
.map(CannedFormattedString::getValue)
.map(TKit::assertTextStream)
.reduce(TKit.TextStreamVerifier::andThen).get());
cmd.execute(1);
}
}