8236138: Add tests for jmod applications

Reviewed-by: herrick, prr
This commit is contained in:
Alexey Semenyuk 2019-12-19 13:39:10 -05:00
parent 68122fd64e
commit 91008df5a8
9 changed files with 238 additions and 63 deletions

View File

@ -28,10 +28,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.test.Functional.ThrowingFunction;
import jdk.jpackage.test.Functional.ThrowingSupplier;
@ -113,7 +113,7 @@ public final class HelloApp {
private JarBuilder createJarBuilder() {
JarBuilder builder = new JarBuilder();
if (appDesc.jarWithMainClass()) {
if (appDesc.isWithMainClass()) {
builder.setMainClass(appDesc.className());
}
return builder;
@ -121,7 +121,6 @@ public final class HelloApp {
void addTo(JPackageCommand cmd) {
final String moduleName = appDesc.moduleName();
final String jarFileName = appDesc.jarFileName();
final String qualifiedClassName = appDesc.className();
if (moduleName != null && appDesc.packageName() == null) {
@ -129,24 +128,32 @@ public final class HelloApp {
"Module [%s] with default package", moduleName));
}
Supplier<Path> getModulePath = () -> {
// `--module-path` option should be set by the moment
// when this action is being executed.
return cmd.getArgumentValue("--module-path", cmd::inputDir, Path::of);
};
if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
// Use Hello.java as is.
cmd.addPrerequisiteAction((self) -> {
Path jarFile = self.inputDir().resolve(jarFileName);
Path jarFile = self.inputDir().resolve(appDesc.jarFileName());
createJarBuilder().setOutputJar(jarFile).addSourceFile(
HELLO_JAVA).create();
});
} else if (appDesc.jmodFileName() != null) {
// Modular app in .jmod file
cmd.addPrerequisiteAction(unused -> {
createBundle(appDesc, getModulePath.get());
});
} else {
cmd.addPrerequisiteAction((self) -> {
// Modular app in .jar file
cmd.addPrerequisiteAction(unused -> {
final Path jarFile;
if (moduleName == null) {
jarFile = self.inputDir().resolve(jarFileName);
jarFile = cmd.inputDir().resolve(appDesc.jarFileName());
} else {
// `--module-path` option should be set by the moment
// when this action is being executed.
jarFile = Path.of(self.getArgumentValue("--module-path",
() -> self.inputDir().toString()), jarFileName);
Files.createDirectories(jarFile.getParent());
jarFile = getModulePath.get().resolve(appDesc.jarFileName());
}
TKit.withTempDirectory("src",
@ -155,7 +162,7 @@ public final class HelloApp {
}
if (moduleName == null) {
cmd.addArguments("--main-jar", jarFileName);
cmd.addArguments("--main-jar", appDesc.jarFileName());
cmd.addArguments("--main-class", qualifiedClassName);
} else {
cmd.addArguments("--module-path", TKit.workDir().resolve(
@ -173,8 +180,7 @@ public final class HelloApp {
}
static JavaAppDesc createDefaltAppDesc() {
return new JavaAppDesc().setClassName(CLASS_NAME).setJarFileName(
"hello.jar");
return new JavaAppDesc().setClassName(CLASS_NAME).setBundleFileName("hello.jar");
}
static void verifyOutputFile(Path outputFile, List<String> args,
@ -205,6 +211,53 @@ public final class HelloApp {
"Check contents of [%s] file", outputFile));
}
public static Path createBundle(JavaAppDesc appDesc, Path outputDir) {
String jmodFileName = appDesc.jmodFileName();
if (jmodFileName != null) {
final Path jmodFilePath = outputDir.resolve(jmodFileName);
TKit.withTempDirectory("jmod-workdir", jmodWorkDir -> {
var jarAppDesc = JavaAppDesc.parse(appDesc.toString())
.setBundleFileName("tmp.jar");
Path jarPath = createBundle(jarAppDesc, jmodWorkDir);
Executor exec = new Executor()
.setToolProvider(JavaTool.JMOD)
.addArguments("create", "--class-path")
.addArgument(jarPath)
.addArgument(jmodFilePath);
if (appDesc.isWithMainClass()) {
exec.addArguments("--main-class", appDesc.className());
}
if (appDesc.moduleVersion() != null) {
exec.addArguments("--module-version", appDesc.moduleVersion());
}
Files.createDirectories(jmodFilePath.getParent());
exec.execute();
});
return jmodFilePath;
}
final JavaAppDesc jarAppDesc;
if (appDesc.isWithBundleFileName()) {
jarAppDesc = appDesc;
} else {
// Create copy of original JavaAppDesc instance.
jarAppDesc = JavaAppDesc.parse(appDesc.toString())
.setBundleFileName(createDefaltAppDesc().jarFileName());
}
JPackageCommand
.helloAppImage(jarAppDesc)
.setArgumentValue("--input", outputDir)
.setArgumentValue("--module-path", outputDir)
.executePrerequisiteActions();
return outputDir.resolve(jarAppDesc.jarFileName());
}
public static void executeLauncherAndVerifyOutput(JPackageCommand cmd,
String... args) {
final Path launcherPath = cmd.appLauncherPath();

View File

@ -309,7 +309,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
return this;
}
JPackageCommand setDefaultAppName() {
public JPackageCommand setDefaultAppName() {
return addArguments("--name", TKit.getCurrentDefaultAppName());
}

View File

@ -40,8 +40,8 @@ public final class JavaAppDesc {
return this;
}
public JavaAppDesc setJarFileName(String v) {
jarFileName = v;
public JavaAppDesc setBundleFileName(String v) {
bundleFileName = v;
return this;
}
@ -50,8 +50,8 @@ public final class JavaAppDesc {
return this;
}
public JavaAppDesc setJarWithMainClass(boolean v) {
jarWithMainClass = v;
public JavaAppDesc setWithMainClass(boolean v) {
withMainClass = v;
return this;
}
@ -77,22 +77,36 @@ public final class JavaAppDesc {
}
public String jarFileName() {
return jarFileName;
if (bundleFileName != null && bundleFileName.endsWith(".jar")) {
return bundleFileName;
}
return null;
}
public String jmodFileName() {
if (bundleFileName != null && bundleFileName.endsWith(".jmod")) {
return bundleFileName;
}
return null;
}
public boolean isWithBundleFileName() {
return bundleFileName != null;
}
public String moduleVersion() {
return moduleVersion;
}
public boolean jarWithMainClass() {
return jarWithMainClass;
public boolean isWithMainClass() {
return withMainClass;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (jarFileName != null) {
sb.append(jarFileName).append(':');
if (bundleFileName != null) {
sb.append(bundleFileName).append(':');
}
if (moduleName != null) {
sb.append(moduleName).append('/');
@ -100,7 +114,7 @@ public final class JavaAppDesc {
if (qualifiedClassName != null) {
sb.append(qualifiedClassName);
}
if (jarWithMainClass) {
if (withMainClass) {
sb.append('!');
}
if (moduleVersion != null) {
@ -113,7 +127,7 @@ public final class JavaAppDesc {
* Create Java application description form encoded string value.
*
* Syntax of encoded Java application description is
* [jar_file:][module_name/]qualified_class_name[!][@module_version].
* [(jar_file|jmods_file):][module_name/]qualified_class_name[!][@module_version].
*
* E.g.: `duke.jar:com.other/com.other.foo.bar.Buz!@3.7` encodes modular
* application. Module name is `com.other`. Main class is
@ -121,6 +135,11 @@ public final class JavaAppDesc {
* compiled and packed in `duke.jar` jar file. jar command will set module
* version (3.7) and main class (Buz) attributes in the jar file.
*
* E.g.: `bar.jmod:com.another/com.another.One` encodes modular
* application. Module name is `com.another`. Main class is
* `com.another.One`. Application will be
* compiled and packed in `bar.jmod` jmod file.
*
* E.g.: `Ciao` encodes non-modular `Ciao` class in the default package.
* jar command will not put main class attribute in the jar file.
* Default name will be picked for jar file - `hello.jar`.
@ -128,7 +147,7 @@ public final class JavaAppDesc {
* @param cmd jpackage command to configure
* @param javaAppDesc encoded Java application description
*/
public static JavaAppDesc parse(String javaAppDesc) {
public static JavaAppDesc parse(final String javaAppDesc) {
JavaAppDesc desc = HelloApp.createDefaltAppDesc();
if (javaAppDesc == null) {
@ -138,7 +157,7 @@ public final class JavaAppDesc {
String moduleNameAndOther = Functional.identity(() -> {
String[] components = javaAppDesc.split(":", 2);
if (components.length == 2) {
desc.setJarFileName(components[0]);
desc.setBundleFileName(components[0]);
}
return components[components.length - 1];
}).get();
@ -156,7 +175,7 @@ public final class JavaAppDesc {
if (components[0].endsWith("!")) {
components[0] = components[0].substring(0,
components[0].length() - 1);
desc.setJarWithMainClass(true);
desc.setWithMainClass(true);
}
desc.setClassName(components[0]);
if (components.length == 2) {
@ -164,12 +183,18 @@ public final class JavaAppDesc {
}
}).run();
if (desc.jmodFileName() != null && desc.moduleName() == null) {
throw new IllegalArgumentException(String.format(
"Java Application Descriptor [%s] is invalid. Non modular app can't be packed in .jmod bundle",
javaAppDesc));
}
return desc;
}
private String qualifiedClassName;
private String moduleName;
private String jarFileName;
private String bundleFileName;
private String moduleVersion;
private boolean jarWithMainClass;
private boolean withMainClass;
}

View File

@ -29,15 +29,14 @@ import java.nio.file.Path;
import java.util.spi.ToolProvider;
public enum JavaTool {
JAVA("java"), JAVAC("javac"), JPACKAGE("jpackage"), JAR("jar"), JLINK("jlink");
JAVA, JAVAC, JPACKAGE, JAR, JLINK, JMOD;
JavaTool(String name) {
this.name = name;
JavaTool() {
this.path = Path.of(System.getProperty("java.home")).resolve(
relativePathInJavaHome()).toAbsolutePath().normalize();
if (!path.toFile().exists()) {
throw new RuntimeException(String.format(
"Unable to find tool [%s] at path=[%s]", name, path));
"Unable to find tool [%s] at path=[%s]", toolName(), path));
}
}
@ -46,17 +45,20 @@ public enum JavaTool {
}
public ToolProvider asToolProvider() {
return ToolProvider.findFirst(name).orElse(null);
return ToolProvider.findFirst(toolName()).orElse(null);
}
Path relativePathInJavaHome() {
Path path = Path.of("bin", name);
Path path = Path.of("bin", toolName());
if (TKit.isWindows()) {
path = path.getParent().resolve(path.getFileName().toString() + ".exe");
}
return path;
}
private String toolName() {
return name().toLowerCase();
}
private Path path;
private String name;
}

View File

@ -181,7 +181,7 @@ public class AdditionalLaunchersTest {
new AdditionalLauncher("ModularAppLauncher")
.addRawProperties(Map.entry("module", JavaAppDesc.parse(
modularAppDesc.toString()).setJarFileName(null).toString()))
modularAppDesc.toString()).setBundleFileName(null).toString()))
.addRawProperties(Map.entry("main-jar", ""))
.applyTo(cmd);

View File

@ -196,11 +196,20 @@ public final class BasicTest {
@Test
// Regular app
@Parameter("Hello")
// Modular app
// Modular app in .jar file
@Parameter("com.other/com.other.Hello")
// Modular app in .jmod file
@Parameter("hello.jmod:com.other/com.other.Hello")
public void testApp(String javaAppDesc) {
JPackageCommand.helloAppImage(javaAppDesc)
.executeAndAssertHelloAppImageCreated();
JavaAppDesc appDesc = JavaAppDesc.parse(javaAppDesc);
JPackageCommand cmd = JPackageCommand.helloAppImage(appDesc);
if (appDesc.jmodFileName() != null) {
// .jmod files are not supported at run-time. They should be
// bundled in Java run-time with jlink command, so disable
// use of external Java run-time if any configured.
cmd.ignoreDefaultRuntime(true);
}
cmd.executeAndAssertHelloAppImageCreated();
}
@Test

View File

@ -77,7 +77,7 @@ public final class MainClassTest {
}
Script withJarMainClass(MainClassType v) {
appDesc.setJarWithMainClass(v != NotSet);
appDesc.setWithMainClass(v != NotSet);
jarMainClass = v;
return this;
}
@ -265,9 +265,10 @@ public final class MainClassTest {
script.appDesc.classFilePath()));
// Create app's jar file with different main class.
var badAppDesc = JavaAppDesc.parse(script.appDesc.toString()).setClassName(
nonExistingMainClass);
JPackageCommand.helloAppImage(badAppDesc).executePrerequisiteActions();
var badAppDesc = JavaAppDesc
.parse(script.appDesc.toString())
.setClassName(nonExistingMainClass);
HelloApp.createBundle(badAppDesc, jarFile.getParent());
// Extract new jar but skip app's class.
explodeJar(jarFile, workDir,

View File

@ -32,10 +32,8 @@ import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Annotations.*;
import jdk.jpackage.test.*;
/*
@ -72,12 +70,26 @@ public final class ModulePathTest {
}
@Test
public void test() throws IOException {
final String moduleName = "com.foo";
JPackageCommand cmd = JPackageCommand.helloAppImage(
"benvenuto.jar:" + moduleName + "/com.foo.Hello");
// Build app jar file.
cmd.executePrerequisiteActions();
@Parameter("benvenuto.jar:com.jar.foo/com.jar.foo.Hello")
@Parameter("benvenuto.jmod:com.jmod.foo/com.jmod.foo.JModHello")
public void test(String javaAppDesc) throws IOException {
JavaAppDesc appDesc = JavaAppDesc.parse(javaAppDesc);
Path goodModulePath = TKit.createTempDirectory("modules");
Path appBundle = HelloApp.createBundle(appDesc, goodModulePath);
JPackageCommand cmd = new JPackageCommand()
.setArgumentValue("--dest", TKit.workDir().resolve("output"))
.setDefaultAppName()
.setPackageType(PackageType.IMAGE);
if (TKit.isWindows()) {
cmd.addArguments("--win-console");
}
cmd.addArguments("--module", String.join("/", appDesc.moduleName(),
appDesc.className()));
// Ignore runtime that can be set for all tests. Usually if default
// runtime is set, it is fake one to save time on running jlink and
@ -85,17 +97,12 @@ public final class ModulePathTest {
// We need proper runtime for this test.
cmd.ignoreDefaultRuntime(true);
// --module-path should be set in JPackageCommand.helloAppImage call
String goodModulePath = Objects.requireNonNull(cmd.getArgumentValue(
"--module-path"));
cmd.removeArgumentWithValue("--module-path");
Path emptyDir = TKit.createTempDirectory("empty-dir");
Path nonExistingDir = TKit.withTempDirectory("non-existing-dir", x -> {});
Function<String, String> substitute = str -> {
String v = str;
v = v.replace(GOOD_PATH, goodModulePath);
v = v.replace(GOOD_PATH, goodModulePath.toString());
v = v.replace(EMPTY_DIR, emptyDir.toString());
v = v.replace(NON_EXISTING_DIR, nonExistingDir.toString());
return v;
@ -116,7 +123,7 @@ public final class ModulePathTest {
expectedErrorMessage = "Error: Missing argument: --runtime-image or --module-path";
} else {
expectedErrorMessage = String.format(
"Error: Module %s not found", moduleName);
"Error: Module %s not found", appDesc.moduleName());
}
List<String> output = cmd

View File

@ -0,0 +1,78 @@
/*
* 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.Path;
import java.util.stream.Stream;
import jdk.jpackage.test.Functional.ThrowingSupplier;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
/*
* @test
* @summary jpackage with --module-path testing
* @library ../../../../helpers
* @build jdk.jpackage.test.*
* @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
* @compile ModulePathTest2.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=jdk.jpackage.tests.ModulePathTest2
*/
public final class ModulePathTest2 {
/**
* Test case for JDK-8233265.
* Adding modules in .jmod files for non-modular app results in unexpected
* jpackage failure.
* @param mainAppDesc
*/
@Test
@Parameter("Hello!")
@Parameter("com.foo/com.foo.ModuleApp")
public void test8233265(String mainAppDesc) throws IOException {
JPackageCommand cmd = JPackageCommand.helloAppImage(mainAppDesc);
// The test should make jpackage invoke jlink.
cmd.ignoreDefaultRuntime(true);
Path modulePath = cmd.getArgumentValue("--module-path", () -> null, Path::of);
if (modulePath == null) {
modulePath = TKit.createTempDirectory("input-modules");
cmd.addArguments("--module-path", modulePath);
}
JavaAppDesc extraModule = JavaAppDesc.parse("x.jmod:com.x/com.x.Y");
HelloApp.createBundle(extraModule, modulePath);
cmd.addArguments("--add-modules", extraModule.moduleName());
cmd.executeAndAssertHelloAppImageCreated();
}
}