diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java index 4e46d9a59cc..b507cc955bd 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -27,14 +27,20 @@ package jdk.jpackage.internal; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Map; import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; import static jdk.jpackage.internal.OverridableResource.createResource; import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; import static jdk.jpackage.internal.StandardBundlerParam.ICON; import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR; import static jdk.jpackage.internal.StandardBundlerParam.APP_CONTENT; +import static jdk.jpackage.internal.StandardBundlerParam.OUTPUT_DIR; +import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; import jdk.jpackage.internal.resources.ResourceLocator; /* @@ -73,8 +79,21 @@ public abstract class AbstractAppImageBuilder { throws IOException { Path inputPath = SOURCE_DIR.fetchFrom(params); if (inputPath != null) { - IOUtils.copyRecursive(SOURCE_DIR.fetchFrom(params), - appLayout.appDirectory()); + inputPath = inputPath.toAbsolutePath(); + + List excludes = new ArrayList<>(); + + for (var path : List.of(TEMP_ROOT.fetchFrom(params), OUTPUT_DIR.fetchFrom(params), root)) { + if (Files.isDirectory(path)) { + path = path.toAbsolutePath(); + if (path.startsWith(inputPath) && !Files.isSameFile(path, inputPath)) { + excludes.add(path); + } + } + } + + IOUtils.copyRecursive(inputPath, + appLayout.appDirectory().toAbsolutePath(), excludes); } AppImageFile.save(root, params); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java index 16cd89d9d52..52579f50296 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -89,9 +89,6 @@ public class Arguments { private List allOptions = null; - private String input = null; - private Path output = null; - private boolean hasMainJar = false; private boolean hasMainClass = false; private boolean hasMainModule = false; @@ -135,9 +132,6 @@ public class Arguments { allOptions = new ArrayList<>(); addLaunchers = new ArrayList<>(); - - output = Paths.get("").toAbsolutePath(); - deployParams.setOutput(output); } // CLIOptions is public for DeployParamsTest @@ -147,13 +141,12 @@ public class Arguments { }), INPUT ("input", "i", OptionCategories.PROPERTY, () -> { - context().input = popArg(); - setOptionValue("input", context().input); + setOptionValue("input", popArg()); }), OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> { - context().output = Path.of(popArg()); - context().deployParams.setOutput(context().output); + var path = Path.of(popArg()); + setOptionValue("dest", path); }), DESCRIPTION ("description", OptionCategories.PROPERTY), @@ -711,7 +704,8 @@ public class Arguments { Map localParams = new HashMap<>(params); try { bundler.validate(localParams); - Path result = bundler.execute(localParams, deployParams.outdir); + Path result = bundler.execute(localParams, + StandardBundlerParam.OUTPUT_DIR.fetchFrom(params)); if (result == null) { throw new PackagerException("MSG_BundlerFailed", bundler.getID(), bundler.getName()); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java index 3a96703e1b5..18645f17a6d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -49,15 +49,9 @@ public class DeployParams { String targetFormat = null; // means default type for this platform - Path outdir = null; - // raw arguments to the bundler Map bundlerArguments = new LinkedHashMap<>(); - public void setOutput(Path output) { - outdir = output; - } - static class Template { Path in; Path out; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java index f4b0483bf98..573109a004b 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java @@ -121,29 +121,52 @@ public class IOUtils { } public static void copyRecursive(Path src, Path dest, - final List excludes, CopyOption... options) + final List excludes, CopyOption... options) throws IOException { + + List copyActions = new ArrayList<>(); + Files.walkFileTree(src, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(final Path dir, - final BasicFileAttributes attrs) throws IOException { - if (excludes.contains(dir.toFile().getName())) { + final BasicFileAttributes attrs) { + if (isPathMatch(dir, excludes)) { return FileVisitResult.SKIP_SUBTREE; } else { - Files.createDirectories(dest.resolve(src.relativize(dir))); + copyActions.add(new CopyAction(null, dest.resolve(src. + relativize(dir)))); return FileVisitResult.CONTINUE; } } @Override public FileVisitResult visitFile(final Path file, - final BasicFileAttributes attrs) throws IOException { - if (!excludes.contains(file.toFile().getName())) { - Files.copy(file, dest.resolve(src.relativize(file)), options); + final BasicFileAttributes attrs) { + if (!isPathMatch(file, excludes)) { + copyActions.add(new CopyAction(file, dest.resolve(src. + relativize(file)))); } return FileVisitResult.CONTINUE; } }); + + for (var copyAction : copyActions) { + copyAction.apply(options); + } + } + + private static record CopyAction(Path src, Path dest) { + void apply(CopyOption... options) throws IOException { + if (src == null) { + Files.createDirectories(dest); + } else { + Files.copy(src, dest, options); + } + } + } + + private static boolean isPathMatch(Path what, List paths) { + return paths.stream().anyMatch(what::endsWith); } public static void copyFile(Path sourceFile, Path destFile) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java index a50293aedb5..718f186c954 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -43,7 +43,6 @@ import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -100,6 +99,14 @@ class StandardBundlerParam extends BundlerParamInfo { (s, p) -> Path.of(s) ); + static final StandardBundlerParam OUTPUT_DIR = + new StandardBundlerParam<>( + Arguments.CLIOptions.OUTPUT.getId(), + Path.class, + p -> Path.of("").toAbsolutePath(), + (s, p) -> Path.of(s) + ); + // note that each bundler is likely to replace this one with // their own converter static final StandardBundlerParam MAIN_JAR = @@ -596,7 +603,7 @@ class StandardBundlerParam extends BundlerParamInfo { } // copy whole runtime, need to skip jmods and src.zip - final List excludes = Arrays.asList("jmods", "src.zip"); + final List excludes = Arrays.asList(Path.of("jmods"), Path.of("src.zip")); IOUtils.copyRecursive(topImage, appLayout.runtimeHomeDirectory(), excludes, LinkOption.NOFOLLOW_LINKS); diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/DirectoryContentVerifierTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/DirectoryContentVerifierTest.java new file mode 100644 index 00000000000..cf910445abf --- /dev/null +++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/DirectoryContentVerifierTest.java @@ -0,0 +1,171 @@ +/* + * 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.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import static java.util.stream.Collectors.toSet; +import java.util.stream.Stream; +import jdk.jpackage.test.Annotations.Parameters; +import jdk.jpackage.test.Annotations.Test; +import static jdk.jpackage.test.DirectoryContentVerifierTest.AssertType.CONTAINS; +import static jdk.jpackage.test.DirectoryContentVerifierTest.AssertType.MATCH; +import jdk.jpackage.test.TKit.DirectoryContentVerifier; +import static jdk.jpackage.test.TKit.assertAssert; + +/* + * @test + * @summary Test TKit.DirectoryContentVerifier from jpackage test library + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @modules jdk.jpackage/jdk.jpackage.internal + * @compile DirectoryContentVerifierTest.java + * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=jdk.jpackage.test.DirectoryContentVerifierTest + */ +public class DirectoryContentVerifierTest { + + enum AssertType { + MATCH(DirectoryContentVerifier::match), + CONTAINS(DirectoryContentVerifier::contains), + ; + + AssertType(BiConsumer> assertFunc) { + this.assertFunc = assertFunc; + } + + private final BiConsumer> assertFunc; + } + + private static ArgsBuilder buildArgs() { + return new ArgsBuilder(); + } + + private static class ArgsBuilder { + + void applyTo(List data) { + data.add(new Object[]{expectedPaths, actualPaths, assertOp, success}); + } + + void applyVariantsTo(List data) { + applyTo(data); + boolean pathGroupsEqual = List.of(expectedPaths).equals(List.of(actualPaths)); + if (assertOp == MATCH) { + if (!pathGroupsEqual) { + data.add(new Object[]{actualPaths, expectedPaths, MATCH, success}); + } + if (success) { + data.add(new Object[]{expectedPaths, actualPaths, CONTAINS, success}); + if (!pathGroupsEqual) { + data.add(new Object[]{actualPaths, expectedPaths, CONTAINS, success}); + } + } + } + } + + ArgsBuilder expectedPaths(String... paths) { + expectedPaths = paths; + return this; + } + + ArgsBuilder actualPaths(String... paths) { + actualPaths = paths; + return this; + } + + ArgsBuilder assertOp(AssertType v) { + assertOp = v; + return this; + } + + ArgsBuilder expectFail() { + success = false; + return this; + } + + private String[] expectedPaths = new String[0]; + private String[] actualPaths = new String[0]; + private AssertType assertOp = MATCH; + private boolean success = true; + } + + @Parameters + public static Collection input() { + List data = new ArrayList<>(); + buildArgs().applyVariantsTo(data); + buildArgs().actualPaths("foo").assertOp(CONTAINS).applyTo(data); + buildArgs().actualPaths("zoo").expectFail().applyVariantsTo(data); + buildArgs().actualPaths("boo").expectedPaths("boo").applyVariantsTo(data); + if (TKit.isWindows()) { + buildArgs().actualPaths("moo").expectedPaths("Moo").applyVariantsTo(data); + } else { + buildArgs().actualPaths("moo").expectedPaths("Moo").expectFail().applyVariantsTo(data); + } + buildArgs().actualPaths("hello").expectedPaths().expectFail().applyVariantsTo(data); + buildArgs().actualPaths("123").expectedPaths("456").expectFail().applyVariantsTo(data); + buildArgs().actualPaths("a", "b", "c").expectedPaths("b", "a", "c").applyVariantsTo(data); + buildArgs().actualPaths("AA", "BB", "CC").expectedPaths("BB", "AA").expectFail().applyVariantsTo(data); + buildArgs().actualPaths("AA", "BB", "CC").expectedPaths("BB", "AA").assertOp(CONTAINS).applyTo(data); + buildArgs().actualPaths("AA", "BB", "CC").expectedPaths("BB", "DD", "AA").expectFail().assertOp(CONTAINS).applyTo(data); + buildArgs().actualPaths("AA", "BB", "CC").expectedPaths("BB", "DD", "AA").expectFail().applyTo(data); + return data; + } + + public DirectoryContentVerifierTest(String[] expectedPaths, String[] actualPaths, + AssertType assertOp, Boolean success) { + this.expectedPaths = conv(expectedPaths); + this.actualPaths = conv(actualPaths); + this.assertOp = assertOp; + this.success = success; + } + + @Test + public void test() { + TKit.withTempDirectory("basedir", this::test); + } + + private void test(Path basedir) throws IOException { + for (var path : actualPaths) { + Files.createFile(basedir.resolve(path)); + } + + var testee = TKit.assertDirectoryContent(basedir); + + assertAssert(success, () -> assertOp.assertFunc.accept(testee, expectedPaths)); + } + + private static Set conv(String... paths) { + return Stream.of(paths).map(Path::of).collect(toSet()); + } + + private final Set expectedPaths; + private final Set actualPaths; + private final AssertType assertOp; + private final boolean success; +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java index 5b4cf3aef71..e5941918938 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java @@ -34,30 +34,35 @@ public class CommandArguments { args = new ArrayList<>(); } - final public T addArgument(String v) { + public final T clearArguments() { + args.clear(); + return (T) this; + } + + public final T addArgument(String v) { args.add(v); return (T) this; } - final public T addArguments(List v) { + public final T addArguments(List v) { args.addAll(v); return (T) this; } - final public T addArgument(Path v) { + public final T addArgument(Path v) { return addArgument(v.toString()); } - final public T addArguments(String... v) { + public final T addArguments(String... v) { return addArguments(Arrays.asList(v)); } - final public T addPathArguments(List v) { + public final T addPathArguments(List v) { return addArguments(v.stream().map((p) -> p.toString()).collect( Collectors.toList())); } - final public List getAllArguments() { + public final List getAllArguments() { return List.copyOf(args); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index fd62d6c7d88..18384db34db 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -52,6 +52,7 @@ import jdk.jpackage.internal.PackageFile; import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.Functional.ThrowingFunction; +import jdk.jpackage.test.Functional.ThrowingRunnable; import jdk.jpackage.test.Functional.ThrowingSupplier; /** @@ -76,6 +77,8 @@ public final class JPackageCommand extends CommandArguments { immutable = cmd.immutable; prerequisiteActions = new Actions(cmd.prerequisiteActions); verifyActions = new Actions(cmd.verifyActions); + appLayoutAsserts = cmd.appLayoutAsserts; + executeInDirectory = cmd.executeInDirectory; } JPackageCommand createImmutableCopy() { @@ -198,7 +201,10 @@ public final class JPackageCommand extends CommandArguments { } public Path outputDir() { - return getArgumentValue("--dest", () -> Path.of("."), Path::of); + var path = getArgumentValue("--dest", () -> Path.of("."), Path::of); + return Optional.ofNullable(executeInDirectory).map(base -> { + return base.resolve(path); + }).orElse(path); } public Path inputDir() { @@ -691,6 +697,12 @@ public final class JPackageCommand extends CommandArguments { return this; } + public JPackageCommand setDirectory(Path v) { + verifyMutable(); + executeInDirectory = v; + return this; + } + public JPackageCommand saveConsoleOutput(boolean v) { verifyMutable(); saveConsoleOutput = v; @@ -733,6 +745,7 @@ public final class JPackageCommand extends CommandArguments { private Executor createExecutor() { Executor exec = new Executor() .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput) + .setDirectory(executeInDirectory) .addArguments(args); if (isWithToolProvider()) { @@ -755,18 +768,19 @@ public final class JPackageCommand extends CommandArguments { executePrerequisiteActions(); if (hasArgument("--dest")) { - if (isImagePackageType()) { - TKit.deleteDirectoryContentsRecursive(outputDir()); - } else { - nullableOutputBundle().ifPresent(path -> { - if (ThrowingSupplier.toSupplier(() -> TKit.deleteIfExists( - path)).get()) { + nullableOutputBundle().ifPresent(path -> { + ThrowingRunnable.toRunnable(() -> { + if (Files.isDirectory(path)) { + TKit.deleteDirectoryRecursive(path, String.format( + "Delete [%s] folder before running jpackage", + path)); + } else if (TKit.deleteIfExists(path)) { TKit.trace(String.format( "Deleted [%s] file before running jpackage", path)); } - }); - } + }).run(); + }); } Path resourceDir = getArgumentValue("--resource-dir", () -> null, Path::of); @@ -816,22 +830,69 @@ public final class JPackageCommand extends CommandArguments { return this; } - JPackageCommand assertAppLayout() { - assertAppImageFile(); - assertPackageFile(); - - TKit.assertDirectoryExists(appRuntimeDirectory()); - - if (!isRuntime()) { - TKit.assertExecutableFileExists(appLauncherPath()); - TKit.assertFileExists(appLauncherCfgPath(null)); - - if (TKit.isOSX()) { - TKit.assertFileExists(appRuntimeDirectory().resolve( - "Contents/MacOS/libjli.dylib")); + public static enum AppLayoutAssert { + APP_IMAGE_FILE(JPackageCommand::assertAppImageFile), + PACKAGE_FILE(JPackageCommand::assertPackageFile), + MAIN_LAUNCHER(cmd -> { + if (cmd.isRuntime()) { + TKit.assertPathExists(convertFromRuntime(cmd).appLauncherPath(), false); + } else { + TKit.assertExecutableFileExists(cmd.appLauncherPath()); } + }), + MAIN_LAUNCHER_CFG_FILE(cmd -> { + if (cmd.isRuntime()) { + TKit.assertPathExists(convertFromRuntime(cmd).appLauncherCfgPath(null), false); + } else { + TKit.assertFileExists(cmd.appLauncherCfgPath(null)); + } + }), + RUNTIME_DIRECTORY(cmd -> { + TKit.assertDirectoryExists(cmd.appRuntimeDirectory()); + if (TKit.isOSX()) { + var libjliPath = cmd.appRuntimeDirectory().resolve("Contents/MacOS/libjli.dylib"); + if (cmd.isRuntime()) { + TKit.assertPathExists(libjliPath, false); + } else { + TKit.assertFileExists(libjliPath); + } + } + }), + MAC_BUNDLE_STRUCTURE(cmd -> { + if (TKit.isOSX()) { + MacHelper.verifyBundleStructure(cmd); + } + }), + ; + + AppLayoutAssert(Consumer action) { + this.action = action; } + private static JPackageCommand convertFromRuntime(JPackageCommand cmd) { + var copy = new JPackageCommand(cmd); + copy.immutable = false; + copy.removeArgumentWithValue("--runtime-image"); + return copy; + } + + private final Consumer action; + } + + public JPackageCommand setAppLayoutAsserts(AppLayoutAssert ... asserts) { + appLayoutAsserts = Set.of(asserts); + return this; + } + + public JPackageCommand excludeAppLayoutAsserts(AppLayoutAssert... asserts) { + return setAppLayoutAsserts(Stream.of(asserts).filter(Predicate.not( + appLayoutAsserts::contains)).toArray(AppLayoutAssert[]::new)); + } + + JPackageCommand assertAppLayout() { + for (var appLayoutAssert : appLayoutAsserts.stream().sorted().toList()) { + appLayoutAssert.action.accept(this); + } return this; } @@ -1111,9 +1172,11 @@ public final class JPackageCommand extends CommandArguments { private boolean immutable; private final Actions prerequisiteActions; private final Actions verifyActions; + private Path executeInDirectory; + private Set appLayoutAsserts = Set.of(AppLayoutAssert.values()); private static boolean defaultWithToolProvider; - private final static Map PACKAGE_TYPES = Functional.identity( + private static final Map PACKAGE_TYPES = Functional.identity( () -> { Map reply = new HashMap<>(); for (PackageType type : PackageType.values()) { @@ -1122,7 +1185,7 @@ public final class JPackageCommand extends CommandArguments { return reply; }).get(); - public final static Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> { + public static final Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> { // Set the property to the path of run-time image to speed up // building app images and platform bundles by avoiding running jlink // The value of the property will be automativcally appended to @@ -1135,5 +1198,5 @@ public final class JPackageCommand extends CommandArguments { return null; }).get(); - private final static String UNPACKED_PATH_ARGNAME = "jpt-unpacked-folder"; + private static final String UNPACKED_PATH_ARGNAME = "jpt-unpacked-folder"; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java index 6a798012ca7..5b49b01a443 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -216,7 +216,9 @@ public final class JavaAppDesc { components[0].length() - 1); desc.setWithMainClass(true); } - desc.setClassName(components[0]); + if (!components[0].isEmpty()) { + desc.setClassName(components[0]); + } if (components.length == 2) { desc.setModuleVersion(components[1]); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java index ded548aface..8068e1d858f 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -35,6 +35,7 @@ import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toSet; import java.util.stream.Stream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -257,6 +258,20 @@ public final class MacHelper { return pkg; } + static void verifyBundleStructure(JPackageCommand cmd) { + Path bundleRoot; + if (cmd.isImagePackageType()) { + bundleRoot = cmd.outputBundle(); + } else { + bundleRoot = cmd.pathToUnpackedPackageFile( + cmd.appInstallationDirectory()); + } + + TKit.assertDirectoryContent(bundleRoot).match(Path.of("Contents")); + TKit.assertDirectoryContent(bundleRoot.resolve("Contents")).match( + cmd.isRuntime() ? RUNTIME_BUNDLE_CONTENTS : APP_BUNDLE_CONTENTS); + } + static String getBundleName(JPackageCommand cmd) { cmd.verifyIsOfType(PackageType.MAC); return String.format("%s-%s%s", getPackageName(cmd), cmd.version(), @@ -390,5 +405,19 @@ public final class MacHelper { static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "Contents/Home/lib/server/libjvm.dylib")); - private final static Method getServicePListFileName = initGetServicePListFileName(); + private static final Method getServicePListFileName = initGetServicePListFileName(); + + private static final Set APP_BUNDLE_CONTENTS = Stream.of( + "Info.plist", + "MacOS", + "app", + "runtime", + "Resources", + "PkgInfo", + "_CodeSignature" + ).map(Path::of).collect(toSet()); + + private static final Set RUNTIME_BUNDLE_CONTENTS = Stream.of( + "Home" + ).map(Path::of).collect(toSet()); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java index d597c62d83f..7882d4cd92d 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -127,6 +127,15 @@ public final class PackageTest extends RunnablePackageTest { return this; } + public PackageTest ignoreBundleOutputDir() { + return ignoreBundleOutputDir(true); + } + + public PackageTest ignoreBundleOutputDir(boolean v) { + ignoreBundleOutputDir = v; + return this; + } + private PackageTest addInitializer(ThrowingConsumer v, String id) { if (id != null) { @@ -368,7 +377,7 @@ public final class PackageTest extends RunnablePackageTest { private final List> handlers; } - final static class PackageHandlers { + static final class PackageHandlers { Consumer installHandler; Consumer uninstallHandler; BiFunction unpackHandler; @@ -528,7 +537,7 @@ public final class PackageTest extends RunnablePackageTest { private final JPackageCommand cmd = Functional.identity(() -> { JPackageCommand result = new JPackageCommand(); result.setDefaultInputOutput().setDefaultAppName(); - if (BUNDLE_OUTPUT_DIR != null) { + if (BUNDLE_OUTPUT_DIR != null && !ignoreBundleOutputDir) { result.setArgumentValue("--dest", BUNDLE_OUTPUT_DIR.toString()); } type.applyTo(result); @@ -777,8 +786,9 @@ public final class PackageTest extends RunnablePackageTest { private Map handlers; private Set namedInitializers; private Map packageHandlers; + private boolean ignoreBundleOutputDir; - private final static File BUNDLE_OUTPUT_DIR; + private static final File BUNDLE_OUTPUT_DIR; static { final String propertyName = "output"; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java index 1466714f8dd..ca1224aafd7 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java @@ -40,10 +40,12 @@ import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -57,13 +59,14 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toSet; import java.util.stream.Stream; import jdk.jpackage.test.Functional.ExceptionBox; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.Functional.ThrowingRunnable; import jdk.jpackage.test.Functional.ThrowingSupplier; -final public class TKit { +public final class TKit { private static final String OS = System.getProperty("os.name").toLowerCase(); @@ -84,7 +87,7 @@ final public class TKit { return TEST_SRC_ROOT.resolve("../../../../src/jdk.jpackage").normalize().toAbsolutePath(); }).get(); - public final static String ICON_SUFFIX = Functional.identity(() -> { + public static final String ICON_SUFFIX = Functional.identity(() -> { if (isOSX()) { return ".icns"; } @@ -273,7 +276,23 @@ final public class TKit { throw new AssertionError(v); } - private final static String TEMP_FILE_PREFIX = null; + static void assertAssert(boolean expectedSuccess, Runnable runnable) { + try { + runnable.run(); + } catch (AssertionError err) { + if (expectedSuccess) { + assertUnexpected("Assertion failed"); + } else { + return; + } + } + + if (!expectedSuccess) { + assertUnexpected("Assertion passed"); + } + } + + private static final String TEMP_FILE_PREFIX = null; private static Path createUniqueFileName(String defaultName) { final String[] nameComponents; @@ -740,6 +759,101 @@ final public class TKit { error(concatMessages("Unexpected", msg)); } + public static DirectoryContentVerifier assertDirectoryContent(Path dir) { + return new DirectoryContentVerifier(dir); + } + + public static final class DirectoryContentVerifier { + public DirectoryContentVerifier(Path baseDir) { + this(baseDir, ThrowingSupplier.toSupplier(() -> { + try (var files = Files.list(baseDir)) { + return files.map(Path::getFileName).collect(toSet()); + } + }).get()); + } + + public void match(Path ... expected) { + DirectoryContentVerifier.this.match(Set.of(expected)); + } + + public void match(Set expected) { + currentTest.notifyAssert(); + + var comm = Comm.compare(content, expected); + if (!comm.unique1.isEmpty() && !comm.unique2.isEmpty()) { + error(String.format( + "assertDirectoryContentEquals(%s): Some expected %s. Unexpected %s. Missing %s", + baseDir, format(comm.common), format(comm.unique1), format(comm.unique2))); + } else if (!comm.unique1.isEmpty()) { + error(String.format( + "assertDirectoryContentEquals%s: Expected %s. Unexpected %s", + baseDir, format(comm.common), format(comm.unique1))); + } else if (!comm.unique2.isEmpty()) { + error(String.format( + "assertDirectoryContentEquals(%s): Some expected %s. Missing %s", + baseDir, format(comm.common), format(comm.unique2))); + } else { + traceAssert(String.format( + "assertDirectoryContentEquals(%s): Expected %s", + baseDir, format(expected))); + } + } + + public void contains(Path ... expected) { + contains(Set.of(expected)); + } + + public void contains(Set expected) { + currentTest.notifyAssert(); + + var comm = Comm.compare(content, expected); + if (!comm.unique2.isEmpty()) { + error(String.format( + "assertDirectoryContentContains(%s): Some expected %s. Missing %s", + baseDir, format(comm.common), format(comm.unique2))); + } else { + traceAssert(String.format( + "assertDirectoryContentContains(%s): Expected %s", + baseDir, format(expected))); + } + } + + public DirectoryContentVerifier removeAll(Path ... paths) { + Set newContent = new HashSet<>(content); + newContent.removeAll(List.of(paths)); + return new DirectoryContentVerifier(baseDir, newContent); + } + + private DirectoryContentVerifier(Path baseDir, Set contents) { + this.baseDir = baseDir; + this.content = contents; + } + + private static record Comm(Set common, Set unique1, Set unique2) { + static Comm compare(Set a, Set b) { + Set common = new HashSet<>(a); + common.retainAll(b); + + Set unique1 = new HashSet<>(a); + unique1.removeAll(common); + + Set unique2 = new HashSet<>(b); + unique2.removeAll(common); + + return new Comm(common, unique1, unique2); + } + } + + private static String format(Set paths) { + return Arrays.toString( + paths.stream().sorted().map(Path::toString).toArray( + String[]::new)); + } + + private final Path baseDir; + private final Set content; + } + public static void assertStringListEquals(List expected, List actual, String msg) { currentTest.notifyAssert(); @@ -817,7 +931,7 @@ final public class TKit { }; } - public final static class TextStreamVerifier { + public static final class TextStreamVerifier { TextStreamVerifier(String value) { this.value = value; predicate(String::contains); @@ -880,7 +994,7 @@ final public class TKit { private String label; private boolean negate; private Supplier createException; - final private String value; + private final String value; } public static TextStreamVerifier assertTextStream(String what) { diff --git a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java index 53fe04509b0..18ca8836790 100644 --- a/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java +++ b/test/jdk/tools/jpackage/junit/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -22,12 +22,9 @@ */ package jdk.jpackage.internal; -import java.nio.file.Path; -import java.io.IOException; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Rule; -import org.junit.Before; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; @@ -40,11 +37,6 @@ public class DeployParamsTest { @Rule public final ExpectedException thrown = ExpectedException.none(); - @Before - public void setUp() throws IOException { - testRoot = tempFolder.newFolder().toPath(); - } - @Test public void testValidAppName() throws PackagerException { initParamsAppName(); @@ -115,7 +107,6 @@ public class DeployParamsTest { private void initParamsAppName() { params = new DeployParams(); - params.setOutput(testRoot); params.addBundleArgument(Arguments.CLIOptions.APPCLASS.getId(), "TestClass"); params.addBundleArgument(Arguments.CLIOptions.MAIN_JAR.getId(), @@ -128,6 +119,5 @@ public class DeployParamsTest { params.validate(); } - private Path testRoot = null; private DeployParams params; } diff --git a/test/jdk/tools/jpackage/share/InOutPathTest.java b/test/jdk/tools/jpackage/share/InOutPathTest.java new file mode 100644 index 00000000000..699d88e3189 --- /dev/null +++ b/test/jdk/tools/jpackage/share/InOutPathTest.java @@ -0,0 +1,266 @@ +/* + * 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. + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import static java.util.stream.Collectors.toSet; +import java.util.stream.Stream; +import jdk.jpackage.internal.AppImageFile; +import jdk.jpackage.internal.ApplicationLayout; +import jdk.jpackage.internal.PackageFile; +import jdk.jpackage.test.Annotations; +import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.Functional.ThrowingConsumer; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.JPackageCommand.AppLayoutAssert; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import static jdk.jpackage.test.RunnablePackageTest.Action.CREATE_AND_UNPACK; +import jdk.jpackage.test.TKit; + +/* + * @test + * @summary Test jpackage command line with overlapping input and output paths + * @library ../helpers + * @build jdk.jpackage.test.* + * @modules jdk.jpackage/jdk.jpackage.internal + * @compile InOutPathTest.java + * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=InOutPathTest + */ +public final class InOutPathTest { + + @Annotations.Parameters + public static Collection input() { + List data = new ArrayList<>(); + + for (var packageTypes : List.of(PackageType.IMAGE.toString(), ALL_NATIVE_PACKAGE_TYPES)) { + data.addAll(List.of(new Object[][]{ + {packageTypes, wrap(InOutPathTest::outputDirInInputDir, "--dest in --input")}, + {packageTypes, wrap(InOutPathTest::outputDirSameAsInputDir, "--dest same as --input")}, + {packageTypes, wrap(InOutPathTest::tempDirInInputDir, "--temp in --input")}, + {packageTypes, wrap(cmd -> { + outputDirInInputDir(cmd); + tempDirInInputDir(cmd); + }, "--dest and --temp in --input")}, + })); + data.addAll(additionalContentInput(packageTypes, "--app-content")); + } + + data.addAll(List.of(new Object[][]{ + {PackageType.IMAGE.toString(), wrap(cmd -> { + additionalContent(cmd, "--app-content", cmd.outputBundle()); + }, "--app-content same as output bundle")}, + })); + + if (TKit.isOSX()) { + data.addAll(additionalContentInput(PackageType.MAC_DMG.toString(), + "--mac-dmg-content")); + } + + return data; + } + + private static List additionalContentInput(String packageTypes, String argName) { + List data = new ArrayList<>(); + + data.addAll(List.of(new Object[][]{ + {packageTypes, wrap(cmd -> { + additionalContent(cmd, argName, cmd.inputDir()); + }, argName + " same as --input")}, + })); + + if (!TKit.isOSX()) { + data.addAll(List.of(new Object[][]{ + {packageTypes, wrap(cmd -> { + additionalContent(cmd, argName, cmd.inputDir().resolve("foo")); + }, argName + " in --input")}, + {packageTypes, wrap(cmd -> { + additionalContent(cmd, argName, cmd.outputDir().resolve("bar")); + }, argName + " in --dest")}, + {packageTypes, wrap(cmd -> { + additionalContent(cmd, argName, cmd.outputDir()); + }, argName + " same as --dest")}, + {packageTypes, wrap(cmd -> { + tempDirInInputDir(cmd); + var tempDir = cmd.getArgumentValue("--temp"); + Files.createDirectories(Path.of(tempDir)); + cmd.addArguments(argName, tempDir); + }, argName + " as --temp; --temp in --input")}, + })); + } + + return data; + } + + public InOutPathTest(String packageTypes, Envelope configure) { + if (ALL_NATIVE_PACKAGE_TYPES.equals(packageTypes)) { + this.packageTypes = PackageType.NATIVE; + } else { + this.packageTypes = Stream.of(packageTypes.split(",")).map( + PackageType::valueOf).collect(toSet()); + } + this.configure = configure.value; + } + + @Test + public void test() throws Throwable { + runTest(packageTypes, configure); + } + + private static Envelope wrap(ThrowingConsumer v, String label) { + return new Envelope(v, label); + } + + private static boolean isAppImageValid(JPackageCommand cmd) { + return !cmd.hasArgument("--app-content") && !cmd.hasArgument("--mac-dmg-content"); + } + + private static void runTest(Set packageTypes, + ThrowingConsumer configure) throws Throwable { + ThrowingConsumer configureWrapper = cmd -> { + // Make sure the input directory is empty in every test run. + // This is needed because jpackage output directories in this test + // are subdirectories of the input directory. + cmd.setInputToEmptyDirectory(); + configure.accept(cmd); + if (cmd.hasArgument("--temp") && cmd.isImagePackageType()) { + // Request to build app image wit user supplied temp directory, + // ignore external runtime if any to make use of the temp directory + // for runtime generation. + cmd.ignoreDefaultRuntime(true); + } else { + cmd.setFakeRuntime(); + } + + if (!isAppImageValid(cmd)) { + // Standard asserts for .jpackage.xml fail in messed up app image. Disable them. + // Other standard asserts for app image contents should pass. + cmd.excludeAppLayoutAsserts(AppLayoutAssert.APP_IMAGE_FILE); + } + }; + + if (packageTypes.contains(PackageType.IMAGE)) { + JPackageCommand cmd = JPackageCommand.helloAppImage(JAR_PATH.toString() + ":"); + configureWrapper.accept(cmd); + cmd.executeAndAssertHelloAppImageCreated(); + if (isAppImageValid(cmd)) { + verifyAppImage(cmd); + } + } else { + new PackageTest() + .forTypes(packageTypes) + .configureHelloApp(JAR_PATH.toString() + ":") + .addInitializer(configureWrapper) + .addInstallVerifier(InOutPathTest::verifyAppImage) + .run(CREATE_AND_UNPACK); + } + } + + private static void outputDirInInputDir(JPackageCommand cmd) throws + IOException { + // Set output dir as a subdir of input dir + Path outputDir = cmd.inputDir().resolve("out"); + TKit.createDirectories(outputDir); + cmd.setArgumentValue("--dest", outputDir); + } + + private static void outputDirSameAsInputDir(JPackageCommand cmd) throws + IOException { + // Set output dir the same as the input dir + cmd.setArgumentValue("--dest", cmd.inputDir()); + } + + private static void tempDirInInputDir(JPackageCommand cmd) { + // Set temp dir as a subdir of input dir + Path tmpDir = cmd.inputDir().resolve("tmp"); + cmd.setArgumentValue("--temp", tmpDir); + } + + private static void additionalContent(JPackageCommand cmd, + String argName, Path base) throws IOException { + Path appContentFile = base.resolve(base.toString().replaceAll("[\\\\/]", + "-") + "-foo.txt"); + TKit.createDirectories(appContentFile.getParent()); + TKit.createTextFile(appContentFile, List.of("Hello Duke!")); + cmd.addArguments(argName, appContentFile.getParent()); + } + + private static void verifyAppImage(JPackageCommand cmd) throws IOException { + if (!isAppImageValid(cmd)) { + // Don't verify the contents of app image as it is invalid. + // jpackage exited without getting stuck in infinite spiral. + // No more expectations from the tool for the give arguments. + return; + } + + final Path rootDir = cmd.isImagePackageType() ? cmd.outputBundle() : cmd.pathToUnpackedPackageFile( + cmd.appInstallationDirectory()); + final Path appDir = ApplicationLayout.platformAppImage().resolveAt( + rootDir).appDirectory(); + + final var knownFiles = Set.of( + JAR_PATH.getName(0).toString(), + PackageFile.getPathInAppImage(Path.of("")).getFileName().toString(), + AppImageFile.getPathInAppImage(Path.of("")).getFileName().toString(), + cmd.name() + ".cfg" + ); + + TKit.assertFileExists(appDir.resolve(JAR_PATH)); + + try (Stream actualFilesStream = Files.list(appDir)) { + var unexpectedFiles = actualFilesStream.map(path -> { + return path.getFileName().toString(); + }).filter(Predicate.not(knownFiles::contains)).toList(); + TKit.assertStringListEquals(List.of(), unexpectedFiles, + "Check there are no unexpected files in `app` folder"); + } + } + + private static final record Envelope(ThrowingConsumer value, String label) { + @Override + public String toString() { + // Will produce the same test description for the same label every + // time it's executed. + // The test runner will keep the same test output directory. + return label; + } + } + + private final Set packageTypes; + private final ThrowingConsumer configure; + + // Placing jar file in the "Resources" subdir of the input directory would allow + // to use the input directory with `--app-content` on OSX. + // For other platforms it doesn't matter. Keep it the same across + // all platforms for simplicity. + private static final Path JAR_PATH = Path.of("Resources/duke.jar"); + + private static final String ALL_NATIVE_PACKAGE_TYPES = "NATIVE"; +} diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java index 72550fd365b..5118600d341 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java +++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.ArrayList; +import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -41,6 +42,8 @@ import jdk.jpackage.test.Executor; import jdk.jpackage.test.JavaTool; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Functional.ThrowingConsumer; +import static jdk.jpackage.test.RunnablePackageTest.Action.CREATE_AND_UNPACK; import static jdk.jpackage.test.WindowsHelper.getTempDirectory; @@ -249,6 +252,63 @@ public final class BasicTest { .executeAndAssertHelloAppImageCreated(); } + @Test + @Parameter("true") + @Parameter("false") + public void testNoOutputDir(boolean appImage) throws Throwable { + var cmd = JPackageCommand.helloAppImage(); + + final var execDir = cmd.outputDir(); + + final ThrowingConsumer initializer = cmdNoOutputDir -> { + cmd.executePrerequisiteActions(); + + final var pkgType = cmdNoOutputDir.packageType(); + + cmdNoOutputDir + .clearArguments() + .addArguments(cmd.getAllArguments()) + // Restore the value of `--type` parameter. + .setPackageType(pkgType) + .removeArgumentWithValue("--dest") + .setArgumentValue("--input", execDir.relativize(cmd.inputDir())) + .setDirectory(execDir) + // Force to use jpackage as executable because we need to + // change the current directory. + .useToolProvider(false); + + Optional.ofNullable(cmdNoOutputDir.getArgumentValue("--runtime-image", + () -> null, Path::of)).ifPresent(runtimePath -> { + if (!runtimePath.isAbsolute()) { + cmdNoOutputDir.setArgumentValue("--runtime-image", + execDir.relativize(runtimePath)); + } + }); + + // JPackageCommand.execute() will not do the cleanup if `--dest` parameter + // is not specified, do it manually. + TKit.createDirectories(execDir); + TKit.deleteDirectoryContentsRecursive(execDir); + }; + + if (appImage) { + var cmdNoOutputDir = new JPackageCommand() + .setPackageType(cmd.packageType()); + initializer.accept(cmdNoOutputDir); + cmdNoOutputDir.executeAndAssertHelloAppImageCreated(); + } else { + // Save time by packing non-functional runtime. + // Build the runtime in app image only. This is sufficient coverage. + cmd.setFakeRuntime(); + new PackageTest() + .addInitializer(initializer) + .addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput) + // Prevent adding `--dest` parameter to jpackage command line. + .ignoreBundleOutputDir() + .run(CREATE_AND_UNPACK); + } + } + @Test @Parameter("ALL-MODULE-PATH") @Parameter("ALL-DEFAULT")