8232279 : Improve test helpers #2
Submitted-by: asemenyuk
Reviewed-by: aherrick, almatvee
/*
* Copyright (c) 2019, 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.tests;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.*;
import jdk.jpackage.test.Functional.ThrowingConsumer;
import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*;
/*
* @test
* @summary test different settings of main class name for jpackage
* @library ../../../../helpers
* @build jdk.jpackage.test.*
* @modules jdk.jpackage/jdk.jpackage.internal
* @compile MainClassTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=jdk.jpackage.tests.MainClassTest
* --jpt-space-subst=_
* --jpt-exclude=modular=y;_main-class=n;_jar-main-class=b;_jlink=y
* --jpt-exclude=modular=y;_main-class=n;_jar-main-class=n;_jlink=n
*/
public final class MainClassTest {
static final class Script {
Script() {
appDesc = JavaAppDesc.parse("test.Hello");
}
Script modular(boolean v) {
appDesc.setModuleName(v ? null : "com.other");
return this;
}
Script withJLink(boolean v) {
withJLink = v;
return this;
}
Script withMainClass(MainClassType v) {
mainClass = v;
return this;
}
Script withJarMainClass(MainClassType v) {
appDesc.setJarWithMainClass(v != NotSet);
jarMainClass = v;
return this;
}
Script expectedErrorMessage(String v) {
expectedErrorMessage = v;
return this;
}
@Override
public String toString() {
return Stream.of(
format("modular", appDesc.moduleName() != null ? 'y' : 'n'),
format("main-class", mainClass),
format("jar-main-class", jarMainClass),
format("jlink", withJLink ? 'y' : 'n'),
format("error", expectedErrorMessage)
).filter(Objects::nonNull).collect(Collectors.joining("; "));
}
private static String format(String key, Object value) {
if (value == null) {
return null;
}
return String.join("=", key, value.toString());
}
enum MainClassType {
NotSet("n"),
SetWrong("b"),
SetRight("y");
MainClassType(String label) {
this.label = label;
}
@Override
public String toString() {
return label;
}
private final String label;
};
private JavaAppDesc appDesc;
private boolean withJLink;
private MainClassType mainClass;
private MainClassType jarMainClass;
private String expectedErrorMessage;
}
public MainClassTest(Script script) {
this.script = script;
nonExistingMainClass = Stream.of(
script.appDesc.packageName(), "ThereIsNoSuchClass").filter(
Objects::nonNull).collect(Collectors.joining("."));
cmd = JPackageCommand.helloAppImage(script.appDesc);
if (!script.withJLink) {
cmd.setFakeRuntime();
}
final String moduleName = script.appDesc.moduleName();
switch (script.mainClass) {
case NotSet:
if (moduleName != null) {
// Don't specify class name, only module name.
cmd.setArgumentValue("--module", moduleName);
} else {
cmd.removeArgumentWithValue("--main-class");
}
break;
case SetWrong:
if (moduleName != null) {
cmd.setArgumentValue("--module",
String.join("/", moduleName, nonExistingMainClass));
} else {
cmd.setArgumentValue("--main-class", nonExistingMainClass);
}
}
}
@Parameters
public static Collection scripts() {
final var withMainClass = Set.of(SetWrong, SetRight);
List<Script[]> scripts = new ArrayList<>();
for (var withJLink : List.of(true, false)) {
for (var modular : List.of(true, false)) {
for (var mainClass : Script.MainClassType.values()) {
for (var jarMainClass : Script.MainClassType.values()) {
if (!withJLink && (jarMainClass == SetWrong || mainClass
== SetWrong)) {
// Without runtime can't run app to verify it will fail, so
// there is no point in such testing.
continue;
}
Script script = new Script()
.modular(modular)
.withJLink(withJLink)
.withMainClass(mainClass)
.withJarMainClass(jarMainClass);
if (withMainClass.contains(jarMainClass)
|| withMainClass.contains(mainClass)) {
} else if (modular) {
script.expectedErrorMessage(
"A main class was not specified nor was one found in the jar");
} else {
script.expectedErrorMessage(
"Error: Main application class is missing");
}
scripts.add(new Script[]{script});
}
}
}
}
return scripts;
}
@Test
public void test() throws IOException {
if (script.jarMainClass == SetWrong) {
initJarWithWrongMainClass();
}
if (script.expectedErrorMessage != null) {
// This is the case when main class is not found nor in jar
// file nor on command line.
List<String> output = cmd
.saveConsoleOutput(true)
.execute()
.assertExitCodeIs(1)
.getOutput();
TKit.assertTextStream(script.expectedErrorMessage).apply(output.stream());
return;
}
// Get here only if main class is specified.
boolean appShouldSucceed = false;
// Should succeed if valid main class is set on the command line.
appShouldSucceed |= (script.mainClass == SetRight);
// Should succeed if main class is not set on the command line but set
// to valid value in the jar.
appShouldSucceed |= (script.mainClass == NotSet && script.jarMainClass == SetRight);
if (appShouldSucceed) {
cmd.executeAndAssertHelloAppImageCreated();
} else {
cmd.executeAndAssertImageCreated();
if (!cmd.isFakeRuntime(String.format("Not running [%s]",
cmd.appLauncherPath()))) {
List<String> output = new Executor()
.setDirectory(cmd.outputDir())
.setExecutable(cmd.appLauncherPath())
.dumpOutput().saveOutput()
.execute().assertExitCodeIs(1).getOutput();
TKit.assertTextStream(String.format(
"Error: Could not find or load main class %s",
nonExistingMainClass)).apply(output.stream());
}
}
}
private void initJarWithWrongMainClass() {
// Call JPackageCommand.executePrerequisiteActions() to build app's jar.
// executePrerequisiteActions() is called by JPackageCommand instance
// only once.
cmd.executePrerequisiteActions();
Path jarFile;
if (script.appDesc.moduleName() != null) {
jarFile = Path.of(cmd.getArgumentValue("--module-path"),
script.appDesc.jarFileName());
} else {
jarFile = cmd.inputDir().resolve(cmd.getArgumentValue("--main-jar"));
}
// Create jar file with the main class attribute in manifest set to
// non-existing class.
TKit.withTempDirectory("repack-jar", workDir -> {
Path manifestFile = workDir.resolve("META-INF/MANIFEST.MF");
try (var jar = new JarFile(jarFile.toFile())) {
jar.stream()
.filter(Predicate.not(JarEntry::isDirectory))
.sequential().forEachOrdered(ThrowingConsumer.toConsumer(
jarEntry -> {
try (var in = jar.getInputStream(jarEntry)) {
Path fileName = workDir.resolve(jarEntry.getName());
Files.createDirectories(fileName.getParent());
Files.copy(in, fileName);
}
}));
}
Files.delete(jarFile);
// Adjust manifest.
TKit.createTextFile(manifestFile, Files.readAllLines(
manifestFile).stream().map(line -> line.replace(
script.appDesc.className(), nonExistingMainClass)));
new Executor().setToolProvider(JavaTool.JAR)
.addArguments("-c", "-M", "-f", jarFile.toString())
.addArguments("-C", workDir.toString(), ".")
.execute().assertExitCodeIsZero();
});
}
private final JPackageCommand cmd;
private final Script script;
private final String nonExistingMainClass;
}