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); prerequisiteActions = new Actions(cmd.prerequisiteActions);
verifyActions = new Actions(cmd.verifyActions); verifyActions = new Actions(cmd.verifyActions);
appLayoutAsserts = cmd.appLayoutAsserts; appLayoutAsserts = cmd.appLayoutAsserts;
outputValidator = cmd.outputValidator;
executeInDirectory = cmd.executeInDirectory; executeInDirectory = cmd.executeInDirectory;
} }
@ -739,6 +740,24 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
return this; 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() { public boolean isWithToolProvider() {
return Optional.ofNullable(withToolProvider).orElse( return Optional.ofNullable(withToolProvider).orElse(
defaultWithToolProvider); defaultWithToolProvider);
@ -817,6 +836,10 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
.createExecutor() .createExecutor()
.execute(expectedExitCode); .execute(expectedExitCode);
if (outputValidator != null) {
outputValidator.accept(result.getOutput().stream());
}
if (result.exitCode == 0) { if (result.exitCode == 0) {
executeVerifyActions(); executeVerifyActions();
} }
@ -1187,6 +1210,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
private final Actions verifyActions; private final Actions verifyActions;
private Path executeInDirectory; private Path executeInDirectory;
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values()); private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
private Consumer<Stream<String>> outputValidator;
private static boolean defaultWithToolProvider; private static boolean defaultWithToolProvider;
private static final Map<String, PackageType> PACKAGE_TYPES = Functional.identity( 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; 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) { public TextStreamVerifier orElseThrow(RuntimeException v) {
return orElseThrow(() -> v); return orElseThrow(() -> v);
} }
@ -956,9 +966,22 @@ public final class TKit {
} }
public void apply(Stream<String> lines) { public void apply(Stream<String> lines) {
String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse( final String matchedStr;
null);
String labelStr = Optional.ofNullable(label).orElse("output"); 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) { if (negate) {
String msg = String.format( String msg = String.format(
"Check %s doesn't contain [%s] string", labelStr, value); "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 BiPredicate<String, String> predicate;
private String label; private String label;
private boolean negate; private boolean negate;
private Supplier<RuntimeException> createException; private Supplier<RuntimeException> createException;
private Consumer<? super Stream<String>> anotherVerifier;
private final String value; private final String value;
} }

View File

@ -24,9 +24,13 @@
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.Annotations.Test;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.TKit; import jdk.jpackage.test.TKit;
/* /*
@ -53,81 +57,85 @@ import jdk.jpackage.test.TKit;
public final class ErrorTest { public final class ErrorTest {
private final String expectedError;
private final JPackageCommand cmd;
@Parameters
public static Collection input() { public static Collection input() {
return List.of(new Object[][]{ return List.of(new Object[][]{
// non-existent arg // non-existent arg
{"Hello", {"Hello",
new String[]{"--no-such-argument"}, new String[]{"--no-such-argument"},
null, null,
"Invalid Option: [--no-such-argument]"}, JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidOption", "--no-such-argument")},
// no main jar // no main jar
{"Hello", {"Hello",
null, null,
new String[]{"--main-jar"}, new String[]{"--main-jar"},
"--main-jar or --module"}, JPackageStringBundle.MAIN.cannedFormattedString("ERR_NoEntryPoint")},
// no main-class // no main-class
{"Hello", {"Hello",
null, null,
new String[]{"--main-class"}, 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 // non-existent main jar
{"Hello", {"Hello",
new String[]{"--main-jar", "non-existent.jar"}, new String[]{"--main-jar", "non-existent.jar"},
null, null,
"main jar does not exist"}, JPackageStringBundle.MAIN.cannedFormattedString("error.main-jar-does-not-exist", "non-existent.jar")},
// non-existent runtime // non-existent runtime
{"Hello", {"Hello",
new String[]{"--runtime-image", "non-existent.runtime"}, new String[]{"--runtime-image", "non-existent.runtime"},
null, null,
"does not exist"}, JPackageStringBundle.MAIN.cannedFormattedString("message.runtime-image-dir-does-not-exist", "runtime-image", "non-existent.runtime")},
// non-existent resource-dir // non-existent resource-dir
{"Hello", {"Hello",
new String[]{"--resource-dir", "non-existent.dir"}, new String[]{"--resource-dir", "non-existent.dir"},
null, null,
"does not exist"}, JPackageStringBundle.MAIN.cannedFormattedString("message.resource-dir-does-not-exist", "resource-dir", "non-existent.dir")},
// invalid type // invalid type
{"Hello", {"Hello",
new String[]{"--type", "invalid-type"}, new String[]{"--type", "invalid-type"},
null, null,
"Invalid or unsupported type: [invalid-type]"}, JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidInstallerType", "invalid-type")},
// no --input // no --input
{"Hello", {"Hello",
null, null,
new String[]{"--input"}, new String[]{"--input"},
"Missing argument: --input"}, JPackageStringBundle.MAIN.cannedFormattedString("ERR_MissingArgument", "--input")},
// no --module-path // no --module-path
{"com.other/com.other.Hello", {"com.other/com.other.Hello",
null, null,
new String[]{"--module-path"}, 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 @Test
public void test() { @ParameterSupplier("input")
List<String> output = cmd.execute(1).getOutput(); public static void test(String javaAppDesc, String[] jpackageArgs,
TKit.assertNotNull(output, "output is null"); String[] removeArgs, CannedFormattedString... expectedErrors) {
TKit.assertTextStream(expectedError).apply(output.stream()); // 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);
}
} }