diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java
new file mode 100644
index 0000000000000..0a7a8cc8d4b24
--- /dev/null
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/IdentityWrapper.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2025, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.internal.util;
+
+import java.util.Objects;
+
+/**
+ * Object wrapper implementing {@link Object#equals(Object)} such that it
+ * returns {@code true} only when the argument is another instance of this class
+ * wrapping the same object.
+ *
+ * The class guarantees that {@link Object#equals(Object)} and
+ * {@link Object#hashCode()} methods of the wrapped object will never be called
+ * inside of the class methods.
+ *
+ * @param the type of the wrapped value
+ */
+public final class IdentityWrapper {
+
+ public IdentityWrapper(T value) {
+ this.value = Objects.requireNonNull(value);
+ }
+
+ public T value() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (getClass() != obj.getClass())) {
+ return false;
+ }
+ var other = (IdentityWrapper>) obj;
+ return value == other.value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Identity[%s]", value);
+ }
+
+ private final T value;
+}
diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitUtilsTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitUtilsTest.java
new file mode 100644
index 0000000000000..28b55f98fe203
--- /dev/null
+++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitUtilsTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025, 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+public class JUnitUtilsTest {
+
+ @Test
+ public void test_assertArrayEquals() {
+ JUnitUtils.assertArrayEquals(new int[] {1, 2, 3}, new int[] {1, 2, 3});
+ JUnitUtils.assertArrayEquals(new long[] {1, 2, 3}, new long[] {1, 2, 3});
+ JUnitUtils.assertArrayEquals(new boolean[] {true, true}, new boolean[] {true, true});
+ }
+
+ @Test
+ public void test_assertArrayEquals_negative() {
+ assertThrows(AssertionError.class, () -> {
+ JUnitUtils.assertArrayEquals(new int[] {1, 2, 3}, new int[] {2, 3});
+ });
+ }
+
+ @Test
+ public void test_exceptionAsPropertyMapWithMessageWithoutCause() {
+
+ var ex = new Exception("foo");
+
+ var map = JUnitUtils.exceptionAsPropertyMap(ex);
+
+ assertEquals(Map.of("getClass", Exception.class.getName(), "getMessage", "foo"), map);
+ }
+}
diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ObjectMapperTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ObjectMapperTest.java
new file mode 100644
index 0000000000000..0310d276e218b
--- /dev/null
+++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/ObjectMapperTest.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright (c) 2025, 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.junit.jupiter.api.Test;
+
+public class ObjectMapperTest {
+
+ @Test
+ public void test_String() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map("foo");
+
+ assertEquals("foo", map);
+ }
+
+ @Test
+ public void test_int() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(100);
+
+ assertEquals(100, map);
+ }
+
+ @Test
+ public void test_null() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(null);
+
+ assertNull(map);
+ }
+
+ @Test
+ public void test_Object() {
+ var obj = new Object();
+ assertSame(obj, ObjectMapper.blank().create().map(obj));
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_Path() {
+ var obj = Path.of("foo/bar");
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_UUID() {
+ var obj = UUID.randomUUID();
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_BigInteger() {
+ var obj = BigInteger.TEN;
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_Enum() {
+
+ var expected = Map.of(
+ "name", TestEnum.BAR.name(),
+ "ordinal", TestEnum.BAR.ordinal(),
+ "a", "A",
+ "b", 123,
+ "num", 100
+ );
+
+ assertEquals(expected, ObjectMapper.standard().create().map(TestEnum.BAR));
+ }
+
+ @Test
+ public void test_array_int() {
+
+ var obj = new int[] { 1, 4, 5 };
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_array_String() {
+
+ var obj = new String[] { "Hello", "Bye" };
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_array_empty() {
+
+ var obj = new Thread[0];
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_array_nulls() {
+
+ var obj = new Thread[10];
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_array_Path() {
+
+ var obj = new Path[] { Path.of("foo/bar"), null, Path.of("").toAbsolutePath() };
+
+ assertSame(obj, ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_array_Object() {
+
+ var obj = new Object[] { Path.of("foo/bar"), null, 145, new Simple.Stub("Hello", 738), "foo" };
+
+ var expected = new Object[] { Path.of("foo/bar"), null, 145, Map.of("a", "Hello", "b", 738), "foo" };
+
+ assertArrayEquals(expected, (Object[])ObjectMapper.standard().create().map(obj));
+ }
+
+ @Test
+ public void test_functional() {
+ assertWrappedIdentity(new Function() {
+
+ @Override
+ public Integer apply(Object o) {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new BiFunction() {
+
+ @Override
+ public Integer apply(Object a, String b) {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new Consumer<>() {
+
+ @Override
+ public void accept(Object o) {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new BiConsumer<>() {
+
+ @Override
+ public void accept(Object a, Object b) {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new Predicate<>() {
+
+ @Override
+ public boolean test(Object o) {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new Supplier<>() {
+
+ @Override
+ public Object get() {
+ throw new AssertionError();
+ }
+
+ });
+
+ assertWrappedIdentity(new Runnable() {
+
+ @Override
+ public void run() {
+ throw new AssertionError();
+ }
+
+ });
+ }
+
+ @Test
+ public void testIdentityWrapper() {
+ var om = ObjectMapper.standard().create();
+
+ var a = new Object() {};
+ var b = new Object() {};
+
+ var amap = om.map(a);
+ var amap2 = om.map(a);
+
+ assertEquals(amap, amap2);
+ assertEquals(ObjectMapper.wrapIdentity(a), amap);
+
+ var bmap = om.map(b);
+
+ assertNotEquals(amap, bmap);
+ assertEquals(ObjectMapper.wrapIdentity(b), bmap);
+ }
+
+ @Test
+ public void test_wrapIdentity() {
+
+ assertThrowsExactly(NullPointerException.class, () -> ObjectMapper.wrapIdentity(null));
+
+ var iw = ObjectMapper.wrapIdentity(new Object());
+
+ assertSame(iw, ObjectMapper.wrapIdentity(iw));
+
+ var simpleStubA = new Simple.Stub("Hello", 77);
+ var simpleStubB = new Simple.Stub("Hello", 77);
+
+ assertEquals(simpleStubA, simpleStubB);
+ assertNotEquals(ObjectMapper.wrapIdentity(simpleStubA), ObjectMapper.wrapIdentity(simpleStubB));
+ assertEquals(ObjectMapper.wrapIdentity(simpleStubA), ObjectMapper.wrapIdentity(simpleStubA));
+ }
+
+ @Test
+ public void test_empty_List() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(List.of());
+
+ assertEquals(List.of(), map);
+ }
+
+ @Test
+ public void test_List() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(List.of(100, "foo"));
+
+ assertEquals(List.of(100, "foo"), map);
+ }
+
+ @Test
+ public void test_empty_Map() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(Map.of());
+
+ assertEquals(Map.of(), map);
+ }
+
+ @Test
+ public void test_Map() {
+ var om = ObjectMapper.blank().create();
+
+ var map = om.map(Map.of(100, "foo"));
+
+ assertEquals(Map.of(100, "foo"), map);
+ }
+
+ @Test
+ public void test_MapSimple() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(Map.of(123, "foo", 321, new Simple.Stub("Hello", 567)));
+
+ assertEquals(Map.of(123, "foo", 321, Map.of("a", "Hello", "b", 567)), map);
+ }
+
+ @Test
+ public void test_ListSimple() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(List.of(100, new Simple.Stub("Hello", 567), "bar", new Simple() {}));
+
+ assertEquals(List.of(100, Map.of("a", "Hello", "b", 567), "bar", Map.of("a", "foo", "b", 123)), map);
+ }
+
+ @Test
+ public void test_Simple() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(new Simple() {});
+
+ assertEquals(Map.of("a", "foo", "b", 123), map);
+ }
+
+ @Test
+ public void test_Proxy() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(Proxy.newProxyInstance(Simple.class.getClassLoader(), new Class>[] { Simple.class }, new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ switch (method.getName()) {
+ case "a" -> {
+ return "Bye";
+ }
+ case "b" -> {
+ return 335;
+ }
+ default -> {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ }));
+
+ assertEquals(Map.of("a", "Bye", "b", 335), map);
+ }
+
+ @Test
+ public void test_Simple_null_property() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(new Simple.Stub(null, 123));
+
+ assertEquals(Map.of("b", 123, "a", ObjectMapper.NULL), map);
+ }
+
+ @Test
+ public void test_Optional_String() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(Optional.of("foo"));
+
+ assertEquals(Map.of("get", "foo"), map);
+ }
+
+ @Test
+ public void test_Optional_empty() {
+ var om = ObjectMapper.standard().create();
+
+ var map = om.map(Optional.empty());
+
+ assertEquals(Map.of("get", ObjectMapper.NULL), map);
+ }
+
+ @Test
+ public void test_toMap() {
+ var om = ObjectMapper.standard().create();
+
+ assertNull(om.toMap(null));
+ assertEquals(Map.of("value", "Hello"), om.toMap("Hello"));
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ }
+
+ @Test
+ public void test_getter_throws() {
+ var om = ObjectMapper.blank()
+ .mutate(ObjectMapper.configureObject())
+ .mutate(ObjectMapper.configureLeafClasses())
+ .mutate(ObjectMapper.configureException())
+ .create();
+
+ var expected = Map.of("get", om.toMap(new UnsupportedOperationException("Not for you!")));
+
+ var actual = om.toMap(new Supplier<>() {
+ @Override
+ public Object get() {
+ throw new UnsupportedOperationException("Not for you!");
+ }
+ });
+
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void test_exception_with_message_with_cause() {
+
+ var ex = new Exception("foo", new IllegalArgumentException("Cause", new RuntimeException("Ops!")));
+
+ var om = ObjectMapper.standard().create();
+
+ var map = om.toMap(ex);
+
+ assertEquals(Map.of(
+ "getClass", Exception.class.getName(),
+ "getMessage", "foo",
+ "getCause", Map.of(
+ "getClass", IllegalArgumentException.class.getName(),
+ "getMessage", "Cause",
+ "getCause", Map.of(
+ "getClass", RuntimeException.class.getName(),
+ "getMessage", "Ops!"
+ )
+ )
+ ), map);
+ }
+
+ @Test
+ public void test_exception_without_message_with_cause() {
+
+ var ex = new RuntimeException(null, new UnknownError("Ops!"));
+
+ var om = ObjectMapper.standard().create();
+
+ var map = om.toMap(ex);
+
+ assertEquals(Map.of(
+ "getClass", RuntimeException.class.getName(),
+ "getCause", Map.of(
+ "getMessage", "Ops!",
+ "getCause", ObjectMapper.NULL
+ )
+ ), map);
+ }
+
+ @Test
+ public void test_exception_without_message_without_cause() {
+
+ var ex = new UnsupportedOperationException();
+
+ var om = ObjectMapper.standard().create();
+
+ var map = om.toMap(ex);
+
+ assertEquals(Map.of("getClass", UnsupportedOperationException.class.getName()), map);
+ }
+
+ @Test
+ public void test_exception_CustomException() {
+
+ var ex = new CustomException("Hello", Path.of(""), Optional.empty(), null);
+
+ var om = ObjectMapper.standard().create();
+
+ var map = om.toMap(ex);
+
+ assertEquals(Map.of(
+ "getClass", CustomException.class.getName(),
+ "getMessage", "Hello",
+ "op", Map.of("get", ObjectMapper.NULL),
+ "path2", Path.of("")
+ ), map);
+ }
+
+ @Test
+ public void test_Builder_accessPackageMethods() {
+
+ var obj = new TestType().foo("Hello").bar(81);
+
+ var map = ObjectMapper.standard().create().toMap(obj);
+
+ assertEquals(Map.of("foo", "Hello"), map);
+
+ map = ObjectMapper.standard().accessPackageMethods(TestType.class.getPackage()).create().toMap(obj);
+
+ assertEquals(Map.of("foo", "Hello", "bar", 81), map);
+ }
+
+ @Test
+ public void test_Builder_methods_Simple() {
+
+ var om = ObjectMapper.standard().exceptSomeMethods(Simple.class).add("a").apply().create();
+
+ assertEquals(Map.of("b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+
+ om = ObjectMapper.standard().exceptSomeMethods(Simple.class).add("b").apply().create();
+
+ assertEquals(Map.of("a", "foo"), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]"), om.toMap(new Simple.DefaultExt("Hello", 345)));
+ }
+
+ @Test
+ public void test_Builder_methods_SimpleStub() {
+
+ var om = ObjectMapper.standard().exceptSomeMethods(Simple.Stub.class).add("a").apply().create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello", "b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]", "b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+
+ om = ObjectMapper.standard().exceptSomeMethods(Simple.Stub.class).add("b").apply().create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello", "b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]", "b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+ }
+
+ @Test
+ public void test_Builder_methods_SimpleDefault() {
+
+ var om = ObjectMapper.standard().exceptSomeMethods(Simple.Default.class).add("a").apply().create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello", "b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+
+ om = ObjectMapper.standard().exceptSomeMethods(Simple.Default.class).add("b").apply().create();
+
+ assertEquals(Map.of("a", "foo"), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]"), om.toMap(new Simple.DefaultExt("Hello", 345)));
+ }
+
+ @Test
+ public void test_Builder_methods_SimpleDefaultExt() {
+
+ var om = ObjectMapper.standard().exceptSomeMethods(Simple.DefaultExt.class).add("a").apply().create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello", "b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello", "b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+
+ om = ObjectMapper.standard().exceptSomeMethods(Simple.DefaultExt.class).add("b").apply().create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello", "b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello", "b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]"), om.toMap(new Simple.DefaultExt("Hello", 345)));
+ }
+
+ @Test
+ public void test_Builder_methods_SimpleStub_and_SimpleDefault() {
+
+ var om = ObjectMapper.standard()
+ .exceptSomeMethods(Simple.Stub.class).add("a").apply()
+ .exceptSomeMethods(Simple.Default.class).add("a").apply()
+ .create();
+
+ assertEquals(Map.of("a", "foo", "b", 123), om.toMap(new Simple() {}));
+ assertEquals(Map.of("b", 345), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("b", 123), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("b", 345 + 10), om.toMap(new Simple.DefaultExt("Hello", 345)));
+
+ om = ObjectMapper.standard()
+ .exceptSomeMethods(Simple.Stub.class).add("b").apply()
+ .exceptSomeMethods(Simple.Default.class).add("b").apply()
+ .create();
+
+ assertEquals(Map.of("a", "foo"), om.toMap(new Simple() {}));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Stub("Hello", 345)));
+ assertEquals(Map.of("a", "Hello"), om.toMap(new Simple.Default("Hello")));
+ assertEquals(Map.of("a", "[Hello]"), om.toMap(new Simple.DefaultExt("Hello", 345)));
+ }
+
+ @Test
+ public void test_Builder_methods_all_excluded() {
+
+ var om = ObjectMapper.standard()
+ .exceptSomeMethods(Simple.class).add("a").apply()
+ .exceptSomeMethods(Simple.Stub.class).add("b").apply()
+ .create();
+
+ var obj = new Simple.Stub("Hello", 345);
+
+ assertEquals(ObjectMapper.wrapIdentity(obj), om.map(obj));
+ }
+
+ interface Simple {
+ default String a() {
+ return "foo";
+ }
+
+ default int b() {
+ return 123;
+ }
+
+ record Stub(String a, int b) implements Simple {}
+
+ static class Default implements Simple {
+ Default(String a) {
+ this.a = a;
+ }
+
+ @Override
+ public String a() {
+ return a;
+ }
+
+ private final String a;
+ }
+
+ static class DefaultExt extends Default {
+ DefaultExt(String a, int b) {
+ super(a);
+ this.b = b;
+ }
+
+ @Override
+ public String a() {
+ return "[" + super.a() + "]";
+ }
+
+ @Override
+ public int b() {
+ return 10 + b;
+ }
+
+ private final int b;
+ }
+ }
+
+ final class TestType {
+
+ public String foo() {
+ return foo;
+ }
+
+ public TestType foo(String v) {
+ foo = v;
+ return this;
+ }
+
+ int bar() {
+ return bar;
+ }
+
+ TestType bar(int v) {
+ bar = v;
+ return this;
+ }
+
+ private String foo;
+ private int bar;
+ }
+
+ enum TestEnum implements Simple {
+ FOO,
+ BAR;
+
+ public int num() {
+ return 100;
+ }
+
+ public int num(int v) {
+ return v;
+ }
+
+ @Override
+ public String a() {
+ return "A";
+ }
+ }
+
+ static final class CustomException extends Exception {
+
+ CustomException(String message, Path path, Optional optional, Throwable cause) {
+ super(message, cause);
+ this.path = path;
+ this.optional = optional;
+ }
+
+ Path path() {
+ return path;
+ }
+
+ public Path path2() {
+ return path;
+ }
+
+ public Optional op() {
+ return optional;
+ }
+
+ private final Path path;
+ private final Optional optional;
+
+ private static final long serialVersionUID = 1L;
+
+ }
+
+ private static void assertWrappedIdentity(ObjectMapper om, Object obj) {
+ var map = om.toMap(obj);
+ assertEquals(Map.of("value", ObjectMapper.wrapIdentity(obj)), map);
+ }
+
+ private static void assertWrappedIdentity(Object obj) {
+ assertWrappedIdentity(ObjectMapper.standard().create(), obj);
+ }
+}
diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/PackageTestTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/PackageTestTest.java
index da94db30925da..4cf89fca3cc8a 100644
--- a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/PackageTestTest.java
+++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/PackageTestTest.java
@@ -341,7 +341,7 @@ public PackageType packageType() {
}
@Override
- JPackageCommand assertAppLayout() {
+ JPackageCommand runStandardAsserts() {
return this;
}
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java
index 50222d89cebdc..66da89fc3f98a 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java
@@ -198,7 +198,7 @@ static void forEachAdditionalLauncher(JPackageCommand cmd,
}
}
- static PropertyFile getAdditionalLauncherProperties(
+ public static PropertyFile getAdditionalLauncherProperties(
JPackageCommand cmd, String launcherName) {
PropertyFile shell[] = new PropertyFile[1];
forEachAdditionalLauncher(cmd, (name, propertiesFilePath) -> {
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ApplicationLayout.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ApplicationLayout.java
index 7ab3b824aa44b..0701421e999f8 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ApplicationLayout.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ApplicationLayout.java
@@ -98,12 +98,18 @@ public static ApplicationLayout platformAppImage() {
throw new IllegalArgumentException("Unknown platform");
}
- public static ApplicationLayout javaRuntime() {
+ public static ApplicationLayout platformJavaRuntime() {
+ Path runtime = Path.of("");
+ Path runtimeHome = runtime;
+ if (TKit.isOSX()) {
+ runtimeHome = Path.of("Contents/Home");
+ }
+
return new ApplicationLayout(
null,
null,
- Path.of(""),
- null,
+ runtime,
+ runtimeHome,
null,
null,
null,
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigurationTarget.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigurationTarget.java
new file mode 100644
index 0000000000000..ba3131a76807d
--- /dev/null
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigurationTarget.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2025, 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.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Provides uniform way to configure {@code JPackageCommand} and
+ * {@code PackageTest} instances.
+ */
+public record ConfigurationTarget(Optional cmd, Optional test) {
+
+ public ConfigurationTarget {
+ Objects.requireNonNull(cmd);
+ Objects.requireNonNull(test);
+ if (cmd.isEmpty() == test.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public ConfigurationTarget(JPackageCommand target) {
+ this(Optional.of(target), Optional.empty());
+ }
+
+ public ConfigurationTarget(PackageTest target) {
+ this(Optional.empty(), Optional.of(target));
+ }
+
+ public ConfigurationTarget apply(Consumer a, Consumer b) {
+ cmd.ifPresent(Objects.requireNonNull(a));
+ test.ifPresent(Objects.requireNonNull(b));
+ return this;
+ }
+
+ public ConfigurationTarget addInitializer(Consumer initializer) {
+ cmd.ifPresent(Objects.requireNonNull(initializer));
+ test.ifPresent(v -> {
+ v.addInitializer(initializer::accept);
+ });
+ return this;
+ }
+
+ public ConfigurationTarget add(AdditionalLauncher addLauncher) {
+ return apply(addLauncher::applyTo, addLauncher::applyTo);
+ }
+}
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java
index 69ea4ecfaa099..6c7b6a2525570 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java
@@ -125,6 +125,8 @@ private JarBuilder createJarBuilder() {
if (appDesc.isWithMainClass()) {
builder.setMainClass(appDesc.className());
}
+ // Use an old release number to make test app classes runnable on older runtimes.
+ builder.setRelease(11);
return builder;
}
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 6945cd2b7223a..22e75a5791125 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java
@@ -67,9 +67,11 @@
*/
public class JPackageCommand extends CommandArguments {
+ @SuppressWarnings("this-escape")
public JPackageCommand() {
prerequisiteActions = new Actions();
verifyActions = new Actions();
+ excludeStandardAsserts(StandardAssert.MAIN_LAUNCHER_DESCRIPTION);
}
private JPackageCommand(JPackageCommand cmd, boolean immutable) {
@@ -85,7 +87,7 @@ private JPackageCommand(JPackageCommand cmd, boolean immutable) {
dmgInstallDir = cmd.dmgInstallDir;
prerequisiteActions = new Actions(cmd.prerequisiteActions);
verifyActions = new Actions(cmd.verifyActions);
- appLayoutAsserts = cmd.appLayoutAsserts;
+ standardAsserts = cmd.standardAsserts;
readOnlyPathAsserts = cmd.readOnlyPathAsserts;
outputValidators = cmd.outputValidators;
executeInDirectory = cmd.executeInDirectory;
@@ -459,7 +461,7 @@ public ApplicationLayout appLayout() {
if (layout != null) {
} else if (isRuntime()) {
- layout = ApplicationLayout.javaRuntime();
+ layout = ApplicationLayout.platformJavaRuntime();
} else {
layout = ApplicationLayout.platformAppImage();
}
@@ -933,7 +935,7 @@ public Executor.Result executeAndAssertImageCreated() {
public JPackageCommand assertImageCreated() {
verifyIsOfType(PackageType.IMAGE);
- assertAppLayout();
+ runStandardAsserts();
return this;
}
@@ -976,10 +978,10 @@ private static final class ReadOnlyPathsAssert {
void updateAndAssert() {
final var newSnapshots = createSnapshots();
for (final var a : asserts.keySet().stream().sorted().toList()) {
- final var snapshopGroup = snapshots.get(a);
- final var newSnapshopGroup = newSnapshots.get(a);
- for (int i = 0; i < snapshopGroup.size(); i++) {
- TKit.PathSnapshot.assertEquals(snapshopGroup.get(i), newSnapshopGroup.get(i),
+ final var snapshotGroup = snapshots.get(a);
+ final var newSnapshotGroup = newSnapshots.get(a);
+ for (int i = 0; i < snapshotGroup.size(); i++) {
+ snapshotGroup.get(i).assertEquals(newSnapshotGroup.get(i),
String.format("Check jpackage didn't modify ${%s}=[%s]", a, asserts.get(a).get(i)));
}
}
@@ -1094,7 +1096,7 @@ public JPackageCommand excludeReadOnlyPathAssert(ReadOnlyPathAssert... asserts)
asSet::contains)).toArray(ReadOnlyPathAssert[]::new));
}
- public static enum AppLayoutAssert {
+ public static enum StandardAssert {
APP_IMAGE_FILE(JPackageCommand::assertAppImageFile),
PACKAGE_FILE(JPackageCommand::assertPackageFile),
NO_MAIN_LAUNCHER_IN_RUNTIME(cmd -> {
@@ -1114,6 +1116,11 @@ public static enum AppLayoutAssert {
LauncherVerifier.Action.VERIFY_MAC_ENTITLEMENTS);
}
}),
+ MAIN_LAUNCHER_DESCRIPTION(cmd -> {
+ if (!cmd.isRuntime()) {
+ new LauncherVerifier(cmd).verify(cmd, LauncherVerifier.Action.VERIFY_DESCRIPTION);
+ }
+ }),
MAIN_JAR_FILE(cmd -> {
Optional.ofNullable(cmd.getArgumentValue("--main-jar", () -> null)).ifPresent(mainJar -> {
TKit.assertFileExists(cmd.appLayout().appDirectory().resolve(mainJar));
@@ -1138,7 +1145,7 @@ public static enum AppLayoutAssert {
}),
;
- AppLayoutAssert(Consumer action) {
+ StandardAssert(Consumer action) {
this.action = action;
}
@@ -1156,21 +1163,21 @@ private static JPackageCommand convertFromRuntime(JPackageCommand cmd) {
private final Consumer action;
}
- public JPackageCommand setAppLayoutAsserts(AppLayoutAssert ... asserts) {
+ public JPackageCommand setStandardAsserts(StandardAssert ... asserts) {
verifyMutable();
- appLayoutAsserts = Set.of(asserts);
+ standardAsserts = Set.of(asserts);
return this;
}
- public JPackageCommand excludeAppLayoutAsserts(AppLayoutAssert... asserts) {
+ public JPackageCommand excludeStandardAsserts(StandardAssert... asserts) {
var asSet = Set.of(asserts);
- return setAppLayoutAsserts(appLayoutAsserts.stream().filter(Predicate.not(
- asSet::contains)).toArray(AppLayoutAssert[]::new));
+ return setStandardAsserts(standardAsserts.stream().filter(Predicate.not(
+ asSet::contains)).toArray(StandardAssert[]::new));
}
- JPackageCommand assertAppLayout() {
- for (var appLayoutAssert : appLayoutAsserts.stream().sorted().toList()) {
- appLayoutAssert.action.accept(this);
+ JPackageCommand runStandardAsserts() {
+ for (var standardAssert : standardAsserts.stream().sorted().toList()) {
+ standardAssert.action.accept(this);
}
return this;
}
@@ -1520,7 +1527,7 @@ public void run() {
private Path winMsiLogFile;
private Path unpackedPackageDirectory;
private Set readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values());
- private Set appLayoutAsserts = Set.of(AppLayoutAssert.values());
+ private Set standardAsserts = Set.of(StandardAssert.values());
private List>> outputValidators = new ArrayList<>();
private static InheritableThreadLocal> defaultToolProvider = new InheritableThreadLocal<>() {
@Override
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java
index d62575d2fefcf..c69c29af53a04 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2025, 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,6 +27,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
/**
@@ -48,6 +49,11 @@ public JarBuilder setMainClass(String v) {
return this;
}
+ public JarBuilder setRelease(int v) {
+ release = v;
+ return this;
+ }
+
public JarBuilder addSourceFile(Path v) {
sourceFiles.add(v);
return this;
@@ -61,11 +67,15 @@ public JarBuilder setModuleVersion(String v) {
public void create() {
TKit.withTempDirectory("jar-workdir", workDir -> {
if (!sourceFiles.isEmpty()) {
- new Executor()
+ var exec = new Executor()
.setToolProvider(JavaTool.JAVAC)
- .addArguments("-d", workDir.toString())
- .addPathArguments(sourceFiles)
- .execute();
+ .addArguments("-d", workDir.toString());
+
+ Optional.ofNullable(release).ifPresent(r -> {
+ exec.addArguments("--release", r.toString());
+ });
+
+ exec.addPathArguments(sourceFiles).execute();
}
Files.createDirectories(outputJar.getParent());
@@ -92,4 +102,5 @@ public void create() {
private Path outputJar;
private String mainClass;
private String moduleVersion;
+ private Integer release;
}
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java
index 278cd569baca7..79652a9828e00 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java
@@ -25,6 +25,7 @@
import java.io.IOException;
import java.nio.file.Path;
+import java.util.Optional;
public final class LauncherIconVerifier {
public LauncherIconVerifier() {
@@ -37,19 +38,33 @@ public LauncherIconVerifier setLauncherName(String v) {
public LauncherIconVerifier setExpectedIcon(Path v) {
expectedIcon = v;
+ expectedDefault = false;
return this;
}
public LauncherIconVerifier setExpectedDefaultIcon() {
+ expectedIcon = null;
expectedDefault = true;
return this;
}
+ public LauncherIconVerifier setExpectedNoIcon() {
+ return setExpectedIcon(null);
+ }
+
public LauncherIconVerifier verifyFileInAppImageOnly(boolean v) {
verifyFileInAppImageOnly = true;
return this;
}
+ public boolean expectDefaultIcon() {
+ return expectedDefault;
+ }
+
+ public Optional expectIcon() {
+ return Optional.ofNullable(expectedIcon);
+ }
+
public void applyTo(JPackageCommand cmd) throws IOException {
final String curLauncherName;
final String label;
@@ -70,7 +85,7 @@ public void applyTo(JPackageCommand cmd) throws IOException {
WinExecutableIconVerifier.verifyLauncherIcon(cmd, launcherName, expectedIcon, expectedDefault);
}
} else if (expectedDefault) {
- TKit.assertPathExists(iconPath, true);
+ TKit.assertFileExists(iconPath);
} else if (expectedIcon == null) {
TKit.assertPathExists(iconPath, false);
} else {
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
index 3a27ae32f437a..9776ab5c4c838 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
@@ -22,7 +22,11 @@
*/
package jdk.jpackage.test;
+import static jdk.jpackage.test.AdditionalLauncher.getAdditionalLauncherProperties;
import static java.util.Collections.unmodifiableSortedSet;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -45,7 +49,6 @@
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
@@ -164,8 +167,7 @@ public static List getPrerequisitePackages(JPackageCommand cmd) {
switch (packageType) {
case LINUX_DEB:
return Stream.of(getDebBundleProperty(cmd.outputBundle(),
- "Depends").split(",")).map(String::strip).collect(
- Collectors.toList());
+ "Depends").split(",")).map(String::strip).toList();
case LINUX_RPM:
return Executor.of("rpm", "-qp", "-R")
@@ -326,10 +328,9 @@ static void verifyPackageBundleEssential(JPackageCommand cmd) {
if (cmd.isRuntime()) {
Path runtimeDir = cmd.appRuntimeDirectory();
Set expectedCriticalRuntimePaths = CRITICAL_RUNTIME_FILES.stream().map(
- runtimeDir::resolve).collect(Collectors.toSet());
+ runtimeDir::resolve).collect(toSet());
Set actualCriticalRuntimePaths = getPackageFiles(cmd).filter(
- expectedCriticalRuntimePaths::contains).collect(
- Collectors.toSet());
+ expectedCriticalRuntimePaths::contains).collect(toSet());
checkPrerequisites = expectedCriticalRuntimePaths.equals(
actualCriticalRuntimePaths);
} else {
@@ -375,8 +376,7 @@ static void addBundleDesktopIntegrationVerifier(PackageTest test, boolean integr
Function, String> verifier = (lines) -> {
// Lookup for xdg commands
return lines.stream().filter(line -> {
- Set words = Stream.of(line.split("\\s+")).collect(
- Collectors.toSet());
+ Set words = Stream.of(line.split("\\s+")).collect(toSet());
return words.contains("xdg-desktop-menu") || words.contains(
"xdg-mime") || words.contains("xdg-icon-resource");
}).findFirst().orElse(null);
@@ -454,11 +454,29 @@ static void verifyDesktopFiles(JPackageCommand cmd, boolean installed) {
}
private static Collection getDesktopFiles(JPackageCommand cmd) {
+
var unpackedDir = cmd.appLayout().desktopIntegrationDirectory();
+
+ return relativePackageFilesInSubdirectory(cmd, ApplicationLayout::desktopIntegrationDirectory)
+ .filter(path -> {
+ return path.getNameCount() == 1;
+ })
+ .filter(path -> {
+ return ".desktop".equals(PathUtils.getSuffix(path));
+ })
+ .map(unpackedDir::resolve)
+ .toList();
+ }
+
+ private static Stream relativePackageFilesInSubdirectory(
+ JPackageCommand cmd, Function subdirFunc) {
+
+ var unpackedDir = subdirFunc.apply(cmd.appLayout());
var packageDir = cmd.pathToPackageFile(unpackedDir);
+
return getPackageFiles(cmd).filter(path -> {
- return packageDir.equals(path.getParent()) && path.getFileName().toString().endsWith(".desktop");
- }).map(Path::getFileName).map(unpackedDir::resolve).toList();
+ return path.startsWith(packageDir);
+ }).map(packageDir::relativize);
}
private static String launcherNameFromDesktopFile(JPackageCommand cmd, Optional predefinedAppImage, Path desktopFile) {
@@ -496,7 +514,20 @@ private static void verifyDesktopFile(JPackageCommand cmd, Optional {
+ return Optional.ofNullable(cmd.getArgumentValue("--description"));
+ }).orElseGet(cmd::name);
+ }
+
+ for (var e : List.of(
+ Map.entry("Type", "Application"),
+ Map.entry("Terminal", "false"),
+ Map.entry("Comment", launcherDescription)
+ )) {
String key = e.getKey();
TKit.assertEquals(e.getValue(), data.find(key).orElseThrow(), String.format(
"Check value of [%s] key", key));
@@ -768,10 +799,10 @@ private static enum Scriptlet {
static final Pattern RPM_HEADER_PATTERN = Pattern.compile(String.format(
"(%s) scriptlet \\(using /bin/sh\\):", Stream.of(values()).map(
- v -> v.rpm).collect(Collectors.joining("|"))));
+ v -> v.rpm).collect(joining("|"))));
static final Map RPM_MAP = Stream.of(values()).collect(
- Collectors.toMap(v -> v.rpm, v -> v));
+ toMap(v -> v.rpm, v -> v));
}
public static String getDefaultPackageArch(PackageType type) {
@@ -848,7 +879,7 @@ private static final class DesktopFile {
} else {
return Map.entry(components[0], components[1]);
}
- }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ObjectMapper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ObjectMapper.java
new file mode 100644
index 0000000000000..f35e255951eeb
--- /dev/null
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ObjectMapper.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (c) 2025, 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 static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toSet;
+import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked;
+import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer;
+import static jdk.jpackage.internal.util.function.ThrowingRunnable.toRunnable;
+import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntPredicate;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLStreamWriter;
+import jdk.jpackage.internal.util.IdentityWrapper;
+
+public final class ObjectMapper {
+
+ private ObjectMapper(
+ Predicate classFilter,
+ Predicate> methodFilter,
+ Predicate leafClassFilter,
+ Map> substitutes,
+ Map, BiConsumer>> mutators,
+ Set accessPackageMethods) {
+
+ this.classFilter = Objects.requireNonNull(classFilter);
+ this.methodFilter = Objects.requireNonNull(methodFilter);
+ this.leafClassFilter = Objects.requireNonNull(leafClassFilter);
+ this.substitutes = Objects.requireNonNull(substitutes);
+ this.mutators = Objects.requireNonNull(mutators);
+ this.accessPackageMethods = accessPackageMethods;
+ }
+
+ public static Builder blank() {
+ return new Builder().allowAllLeafClasses(false).exceptLeafClasses().add(Stream.of(
+ Object.class,
+ String.class, String[].class,
+ boolean.class, Boolean.class, boolean[].class, Boolean[].class,
+ byte.class, Byte.class, byte[].class, Byte[].class,
+ char.class, Character.class, char[].class, Character[].class,
+ short.class, Short.class, short[].class, Short[].class,
+ int.class, Integer.class, int[].class, Integer[].class,
+ long.class, Long.class, long[].class, Long[].class,
+ float.class, Float.class, float[].class, Float[].class,
+ double.class, Double.class, double[].class, Double[].class,
+ void.class, Void.class, Void[].class
+ ).map(Class::getName).toList()).apply();
+ }
+
+ public static Builder standard() {
+ return blank()
+ .mutate(configureObject())
+ .mutate(configureLeafClasses())
+ .mutate(configureOptional())
+ .mutate(configureFunctionalTypes())
+ .mutate(configureEnum())
+ .mutate(configureException());
+ }
+
+ public static Consumer configureObject() {
+ // Exclude all method of Object class.
+ return builder -> {
+ builder.exceptMethods().add(OBJECT_METHODS).apply();
+ };
+ }
+
+ public static Consumer configureLeafClasses() {
+ return builder -> {
+ builder.exceptLeafClasses().add(Stream.of(
+ IdentityWrapper.class,
+ Class.class,
+ Path.class,
+ Path.of("").getClass(),
+ UUID.class,
+ BigInteger.class
+ ).map(Class::getName).toList()).apply();
+ };
+ }
+
+ public static Consumer configureOptional() {
+ return builder -> {
+ // Filter out all but "get()" methods of "Optional" class.
+ builder.exceptAllMethods(Optional.class).remove("get").apply();
+ // Substitute "Optional.get()" with the function that will return "null" if the value is "null".
+ builder.subst(Optional.class, "get", opt -> {
+ if (opt.isPresent()) {
+ return opt.get();
+ } else {
+ return null;
+ }
+ });
+ };
+ }
+
+ public static Consumer configureFunctionalTypes() {
+ // Remove all getters from the standard functional types.
+ return builder -> {
+ builder.exceptAllMethods(Predicate.class).apply();
+ builder.exceptAllMethods(Supplier.class).apply();
+ };
+ }
+
+ public static Consumer configureEnum() {
+ return builder -> {
+ // Filter out "getDeclaringClass()" and "describeConstable()" methods of "Enum" class.
+ builder.exceptSomeMethods(Enum.class).add("getDeclaringClass", "describeConstable").apply();
+ };
+ }
+
+ public static Consumer configureException() {
+ return builder -> {
+ // Include only "getMessage()" and "getCause()" methods of "Exception" class.
+ builder.exceptAllMethods(Exception.class).remove("getMessage", "getCause").apply();
+ builder.mutator(Exception.class, (ex, map) -> {
+ var eit = map.entrySet().iterator();
+ while (eit.hasNext()) {
+ var e = eit.next();
+ if (e.getValue() == NULL) {
+ // Remove property with the "null" value.
+ eit.remove();
+ }
+ }
+ map.put("getClass", ex.getClass().getName());
+ });
+ };
+ }
+
+ public static String lookupFullMethodName(Method m) {
+ return lookupFullMethodName(m.getDeclaringClass(), m.getName());
+ }
+
+ public static String lookupFullMethodName(Class> c, String m) {
+ return Objects.requireNonNull(c).getName() + lookupMethodName(m);
+ }
+
+ public static String lookupMethodName(Method m) {
+ return lookupMethodName(m.getName());
+ }
+
+ public static String lookupMethodName(String m) {
+ return "#" + Objects.requireNonNull(m);
+ }
+
+ public static Object wrapIdentity(Object v) {
+ if (v instanceof IdentityWrapper> wrapper) {
+ return wrapper;
+ } else {
+ return new IdentityWrapper(v);
+ }
+ }
+
+ public static void store(Map map, XMLStreamWriter xml) {
+ XmlWriter.writePropertyMap(map, xml);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Optional findNonNullProperty(Map map, String propertyName) {
+ Objects.requireNonNull(propertyName);
+ Objects.requireNonNull(map);
+
+ return Optional.ofNullable(map.get(propertyName)).filter(Predicate.not(NULL::equals)).map(v -> {
+ return (T)v;
+ });
+ }
+
+ public Object map(Object obj) {
+ if (obj != null) {
+ return mapObject(obj).orElseGet(Map::of);
+ } else {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map toMap(Object obj) {
+ if (obj == null) {
+ return null;
+ } else {
+ var mappedObj = map(obj);
+ if (mappedObj instanceof Map, ?> m) {
+ return (Map)m;
+ } else {
+ return Map.of("value", mappedObj);
+ }
+ }
+ }
+
+ public Optional mapObject(Object obj) {
+ if (obj == null) {
+ return Optional.empty();
+ }
+
+ if (leafClassFilter.test(obj.getClass().getName())) {
+ return Optional.of(obj);
+ }
+
+ if (!filter(obj.getClass())) {
+ return Optional.empty();
+ }
+
+ if (obj instanceof Iterable> col) {
+ return Optional.of(mapIterable(col));
+ }
+
+ if (obj instanceof Map, ?> map) {
+ return Optional.of(mapMap(map));
+ }
+
+ if (obj.getClass().isArray()) {
+ return Optional.of(mapArray(obj));
+ }
+
+ var theMap = getMethods(obj).map(m -> {
+ final Object propertyValue;
+ final var subst = substitutes.get(m);
+ if (subst != null) {
+ propertyValue = applyGetter(obj, subst);
+ } else {
+ propertyValue = invoke(m, obj);
+ }
+ return Map.entry(m.getName(), mapObject(propertyValue).orElse(NULL));
+ }).collect(toMutableMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ mutators.entrySet().stream().filter(m -> {
+ return m.getKey().isInstance(obj);
+ }).findFirst().ifPresent(m -> {
+ m.getValue().accept(obj, theMap);
+ });
+
+ if (theMap.isEmpty()) {
+ return Optional.of(wrapIdentity(obj));
+ }
+
+ return Optional.of(theMap);
+ }
+
+ private Object invoke(Method m, Object obj) {
+ try {
+ return m.invoke(obj);
+ } catch (IllegalAccessException ex) {
+ throw rethrowUnchecked(ex);
+ } catch (InvocationTargetException ex) {
+ return map(ex.getTargetException());
+ }
+ }
+
+ private Collection mapIterable(Iterable> col) {
+ final List list = new ArrayList<>();
+ for (var obj : col) {
+ list.add(mapObject(obj).orElse(NULL));
+ }
+ return list;
+ }
+
+ private Map mapMap(Map, ?> map) {
+ return map.entrySet().stream().collect(toMutableMap(e -> {
+ return mapObject(e.getKey()).orElse(NULL);
+ }, e -> {
+ return mapObject(e.getValue()).orElse(NULL);
+ }));
+ }
+
+ private Object mapArray(Object arr) {
+ final var len = Array.getLength(arr);
+
+ if (len == 0) {
+ return arr;
+ }
+
+ Object[] buf = null;
+
+ for (int i = 0; i != len; i++) {
+ var from = Array.get(arr, i);
+ if (from != null) {
+ var to = mapObject(from).orElseThrow();
+ if (from != to || buf != null) {
+ if (buf == null) {
+ buf = (Object[])Array.newInstance(Object.class, len);
+ System.arraycopy(arr, 0, buf, 0, i);
+ }
+ buf[i] = to;
+ }
+ }
+ }
+
+ return Optional.ofNullable((Object)buf).orElse(arr);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object applyGetter(Object obj, Function getter) {
+ return getter.apply((T)obj);
+ }
+
+ private boolean filter(Class> type) {
+ return classFilter.test(type.getName());
+ }
+
+ private boolean filter(Method m) {
+ return methodFilter.test(List.of(lookupMethodName(m), lookupFullMethodName(m)));
+ }
+
+ private Stream getMethods(Object obj) {
+ return MethodGroups.create(obj.getClass(), accessPackageMethods).filter(this::filter).map(MethodGroup::callable);
+ }
+
+ private static boolean defaultFilter(Method m) {
+ if (Modifier.isStatic(m.getModifiers()) || (m.getParameterCount() > 0) || void.class.equals(m.getReturnType())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static
+ Collector> toMutableMap(Function super T, ? extends K> keyMapper,
+ Function super T, ? extends U> valueMapper) {
+ return Collectors.toMap(keyMapper, valueMapper, (x , y) -> {
+ throw new UnsupportedOperationException(
+ String.format("Entries with the same key and different values [%s] and [%s]", x, y));
+ }, HashMap::new);
+ }
+
+ public static final class Builder {
+
+ private Builder() {
+ allowAllClasses();
+ allowAllLeafClasses();
+ allowAllMethods();
+ }
+
+ public ObjectMapper create() {
+ return new ObjectMapper(
+ classFilter.createPredicate(),
+ methodFilter.createMultiPredicate(),
+ leafClassFilter.createPredicate(),
+ Map.copyOf(substitutes),
+ Map.copyOf(mutators),
+ accessPackageMethods);
+ }
+
+
+ public final class NamePredicateBuilder {
+
+ NamePredicateBuilder(Filter sink) {
+ this.sink = Objects.requireNonNull(sink);
+ }
+
+ public Builder apply() {
+ sink.addAll(items);
+ return Builder.this;
+ }
+
+ public NamePredicateBuilder add(String... v) {
+ return add(List.of(v));
+ }
+
+ public NamePredicateBuilder add(Collection v) {
+ items.addAll(v);
+ return this;
+ }
+
+ private final Filter sink;
+ private final Set items = new HashSet<>();
+ }
+
+
+ public final class AllMethodPredicateBuilder {
+
+ AllMethodPredicateBuilder(Class> type) {
+ impl = new MethodPredicateBuilder(type, false);
+ }
+
+ public AllMethodPredicateBuilder remove(String... v) {
+ return remove(List.of(v));
+ }
+
+ public AllMethodPredicateBuilder remove(Collection v) {
+ impl.add(v);
+ return this;
+ }
+
+ public Builder apply() {
+ return impl.apply();
+ }
+
+ private final MethodPredicateBuilder impl;
+ }
+
+
+ public final class SomeMethodPredicateBuilder {
+
+ SomeMethodPredicateBuilder(Class> type) {
+ impl = new MethodPredicateBuilder(type, true);
+ }
+
+ public SomeMethodPredicateBuilder add(String... v) {
+ return add(List.of(v));
+ }
+
+ public SomeMethodPredicateBuilder add(Collection v) {
+ impl.add(v);
+ return this;
+ }
+
+ public Builder apply() {
+ return impl.apply();
+ }
+
+ private final MethodPredicateBuilder impl;
+ }
+
+
+ public Builder allowAllClasses(boolean v) {
+ classFilter.negate(v);
+ return this;
+ }
+
+ public Builder allowAllClasses() {
+ return allowAllClasses(true);
+ }
+
+ public Builder allowAllMethods(boolean v) {
+ methodFilter.negate(v);
+ return this;
+ }
+
+ public Builder allowAllMethods() {
+ return allowAllMethods(true);
+ }
+
+ public Builder allowAllLeafClasses(boolean v) {
+ leafClassFilter.negate(v);
+ return this;
+ }
+
+ public Builder allowAllLeafClasses() {
+ return allowAllLeafClasses(true);
+ }
+
+ public NamePredicateBuilder exceptClasses() {
+ return new NamePredicateBuilder(classFilter);
+ }
+
+ public AllMethodPredicateBuilder exceptAllMethods(Class> type) {
+ return new AllMethodPredicateBuilder(type);
+ }
+
+ public SomeMethodPredicateBuilder exceptSomeMethods(Class> type) {
+ return new SomeMethodPredicateBuilder(type);
+ }
+
+ public NamePredicateBuilder exceptMethods() {
+ return new NamePredicateBuilder(methodFilter);
+ }
+
+ public NamePredicateBuilder exceptLeafClasses() {
+ return new NamePredicateBuilder(leafClassFilter);
+ }
+
+ public Builder subst(Method target, Function, Object> substitute) {
+ substitutes.put(Objects.requireNonNull(target), Objects.requireNonNull(substitute));
+ return this;
+ }
+
+ public Builder subst(Class extends T> targetClass, String targetMethodName, Function substitute) {
+ var method = toSupplier(() -> targetClass.getMethod(targetMethodName)).get();
+ return subst(method, substitute);
+ }
+
+ public Builder mutator(Class> targetClass, BiConsumer> mutator) {
+ mutators.put(Objects.requireNonNull(targetClass), Objects.requireNonNull(mutator));
+ return this;
+ }
+
+ public Builder mutate(Consumer mutator) {
+ mutator.accept(this);
+ return this;
+ }
+
+ public Builder accessPackageMethods(Package... packages) {
+ Stream.of(packages).map(Package::getName).forEach(accessPackageMethods::add);
+ return this;
+ }
+
+
+ private final class MethodPredicateBuilder {
+
+ MethodPredicateBuilder(Class> type, boolean negate) {
+ this.type = Objects.requireNonNull(type);
+ buffer.negate(negate);
+ }
+
+ void add(Collection v) {
+ buffer.addAll(v);
+ }
+
+ Builder apply() {
+ var pred = buffer.createPredicate();
+
+ var items = MethodGroups.create(type, accessPackageMethods).groups().stream().map(MethodGroup::primary).filter(m -> {
+ return !OBJECT_METHODS.contains(ObjectMapper.lookupMethodName(m));
+ }).filter(m -> {
+ return !pred.test(m.getName());
+ }).map(ObjectMapper::lookupFullMethodName).toList();
+
+ return exceptMethods().add(items).apply();
+ }
+
+ private final Class> type;
+ private final Filter buffer = new Filter();
+ }
+
+
+ private static final class Filter {
+ Predicate> createMultiPredicate() {
+ if (items.isEmpty()) {
+ var match = negate;
+ return v -> match;
+ } else if (negate) {
+ return v -> {
+ return v.stream().noneMatch(Set.copyOf(items)::contains);
+ };
+ } else {
+ return v -> {
+ return v.stream().anyMatch(Set.copyOf(items)::contains);
+ };
+ }
+ }
+
+ Predicate createPredicate() {
+ if (items.isEmpty()) {
+ var match = negate;
+ return v -> match;
+ } else if (negate) {
+ return Predicate.not(Set.copyOf(items)::contains);
+ } else {
+ return Set.copyOf(items)::contains;
+ }
+ }
+
+ void addAll(Collection v) {
+ items.addAll(v);
+ }
+
+ void negate(boolean v) {
+ negate = v;
+ }
+
+ private boolean negate;
+ private final Set items = new HashSet<>();
+ }
+
+
+ private final Filter classFilter = new Filter();
+ private final Filter methodFilter = new Filter();
+ private final Filter leafClassFilter = new Filter();
+ private final Map> substitutes = new HashMap<>();
+ private final Map, BiConsumer>> mutators = new HashMap<>();
+ private final Set accessPackageMethods = new HashSet<>();
+ }
+
+
+ private record MethodGroup(List methods) {
+
+ MethodGroup {
+ Objects.requireNonNull(methods);
+
+ if (methods.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+
+ methods.stream().map(Method::getName).reduce((a, b) -> {
+ if (!a.equals(b)) {
+ throw new IllegalArgumentException();
+ } else {
+ return a;
+ }
+ });
+ }
+
+ Method callable() {
+ var primary = primary();
+ if (!primary.getDeclaringClass().isInterface()) {
+ primary = methods.stream().filter(m -> {
+ return m.getDeclaringClass().isInterface();
+ }).findFirst().orElse(primary);
+ }
+ return primary;
+ }
+
+ Method primary() {
+ return methods.getFirst();
+ }
+
+ boolean match(Predicate predicate) {
+ Objects.requireNonNull(predicate);
+ return methods.stream().allMatch(predicate);
+ }
+ }
+
+
+ private record MethodGroups(Collection groups) {
+
+ MethodGroups {
+ Objects.requireNonNull(groups);
+ }
+
+ Stream filter(Predicate predicate) {
+ Objects.requireNonNull(predicate);
+
+ return groups.stream().filter(g -> {
+ return g.match(predicate);
+ });
+ }
+
+ static MethodGroups create(Class> type, Set accessPackageMethods) {
+ List> types = new ArrayList<>();
+
+ collectSuperclassAndInterfaces(type, types::add);
+
+ final var methodGroups = types.stream()
+ .map(c -> {
+ if (accessPackageMethods.contains(c.getPackageName())) {
+ return PUBLIC_AND_PACKAGE_METHODS_GETTER.apply(c);
+ } else {
+ return PUBLIC_METHODS_GETTER.apply(c);
+ }
+ })
+ .flatMap(x -> x)
+ .filter(ObjectMapper::defaultFilter)
+ .collect(groupingBy(Method::getName));
+
+ return new MethodGroups(methodGroups.values().stream().distinct().map(MethodGroup::new).toList());
+ }
+
+ private static void collectSuperclassAndInterfaces(Class> type, Consumer> sink) {
+ Objects.requireNonNull(type);
+ Objects.requireNonNull(sink);
+
+ for (; type != null; type = type.getSuperclass()) {
+ sink.accept(type);
+ for (var i : type.getInterfaces()) {
+ collectSuperclassAndInterfaces(i, sink);
+ }
+ }
+ }
+ }
+
+
+ private static final class XmlWriter {
+ static void write(Object obj, XMLStreamWriter xml) {
+ if (obj instanceof Map, ?> map) {
+ writePropertyMap(map, xml);
+ } else if (obj instanceof Collection> col) {
+ writeCollection(col, xml);
+ } else if (obj.getClass().isArray()) {
+ writeArray(obj, xml);
+ } else {
+ toRunnable(() -> xml.writeCharacters(obj.toString())).run();
+ }
+ }
+
+ private static void writePropertyMap(Map, ?> map, XMLStreamWriter xml) {
+ map.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().toString())).forEach(toConsumer(e -> {
+ xml.writeStartElement("property");
+ xml.writeAttribute("name", e.getKey().toString());
+ write(e.getValue(), xml);
+ xml.writeEndElement();
+ }));
+ }
+
+ private static void writeCollection(Collection> col, XMLStreamWriter xml) {
+ try {
+ xml.writeStartElement("collection");
+ xml.writeAttribute("size", Integer.toString(col.size()));
+ for (var item : col) {
+ xml.writeStartElement("item");
+ write(item, xml);
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ } catch (Exception ex) {
+ rethrowUnchecked(ex);
+ }
+ }
+
+ private static void writeArray(Object arr, XMLStreamWriter xml) {
+ var len = Array.getLength(arr);
+ try {
+ xml.writeStartElement("array");
+ xml.writeAttribute("size", Integer.toString(len));
+ for (int i = 0; i != len; i++) {
+ xml.writeStartElement("item");
+ write(Array.get(arr, i), xml);
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ } catch (Exception ex) {
+ rethrowUnchecked(ex);
+ }
+ }
+ }
+
+
+ private final Predicate classFilter;
+ private final Predicate> methodFilter;
+ private final Predicate leafClassFilter;
+ private final Map> substitutes;
+ private final Map, BiConsumer>> mutators;
+ private final Set accessPackageMethods;
+
+ static final Object NULL = new Object() {
+ @Override
+ public String toString() {
+ return "";
+ }
+ };
+
+ private static final Set OBJECT_METHODS =
+ Stream.of(Object.class.getMethods()).map(ObjectMapper::lookupMethodName).collect(toSet());
+
+ private static final Function, Stream> PUBLIC_METHODS_GETTER = type -> {
+ return Stream.of(type.getMethods());
+ };
+
+ private static final Function, Stream> PUBLIC_AND_PACKAGE_METHODS_GETTER = type -> {
+ return Stream.of(type.getDeclaredMethods()).filter(m -> {
+ return Stream.of(Modifier::isPrivate, Modifier::isProtected).map(p -> {
+ return p.test(m.getModifiers());
+ }).allMatch(v -> !v);
+ }).map(m -> {
+ m.setAccessible(true);
+ return m;
+ });
+ };
+}
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 84453038cd2c8..3226811fe36e3 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java
@@ -39,7 +39,6 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
@@ -318,6 +317,11 @@ PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa) {
return this;
}
+ public PackageTest mutate(Consumer mutator) {
+ mutator.accept(this);
+ return this;
+ }
+
public PackageTest forTypes(Collection types, Runnable action) {
final var oldTypes = Set.of(currentTypes.toArray(PackageType[]::new));
try {
@@ -334,7 +338,11 @@ public PackageTest forTypes(PackageType type, Runnable action) {
}
public PackageTest forTypes(PackageType type, Consumer action) {
- return forTypes(List.of(type), () -> action.accept(this));
+ return forTypes(List.of(type), action);
+ }
+
+ public PackageTest forTypes(Collection types, Consumer action) {
+ return forTypes(types, () -> action.accept(this));
}
public PackageTest notForTypes(Collection types, Runnable action) {
@@ -348,7 +356,11 @@ public PackageTest notForTypes(PackageType type, Runnable action) {
}
public PackageTest notForTypes(PackageType type, Consumer action) {
- return notForTypes(List.of(type), () -> action.accept(this));
+ return notForTypes(List.of(type), action);
+ }
+
+ public PackageTest notForTypes(Collection types, Consumer action) {
+ return notForTypes(types, () -> action.accept(this));
}
public PackageTest configureHelloApp() {
@@ -780,7 +792,7 @@ private void verifyPackageInstalled(JPackageCommand cmd) {
LauncherAsServiceVerifier.verify(cmd);
}
- cmd.assertAppLayout();
+ cmd.runStandardAsserts();
installVerifiers.forEach(v -> v.accept(cmd));
}
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 bdf9fb85672c1..a19b3697a8159 100644
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
@@ -1260,8 +1260,8 @@ public PathSnapshot(Path path) {
this(hashRecursive(path));
}
- public static void assertEquals(PathSnapshot a, PathSnapshot b, String msg) {
- assertStringListEquals(a.contentHashes(), b.contentHashes(), msg);
+ public void assertEquals(PathSnapshot other, String msg) {
+ assertStringListEquals(contentHashes(), other.contentHashes(), msg);
}
private static List hashRecursive(Path path) {
diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/IdentityWrapperTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/IdentityWrapperTest.java
new file mode 100644
index 0000000000000..471a7cb55a966
--- /dev/null
+++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/IdentityWrapperTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2025, 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.internal.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+
+
+public class IdentityWrapperTest {
+
+ @Test
+ public void test_null() {
+ assertThrows(NullPointerException.class, () -> identityOf(null));
+ }
+
+ @Test
+ public void test_equals() {
+ var obj = new TestRecord(10);
+ assertEquals(identityOf(obj), identityOf(obj));
+ }
+
+ @Test
+ public void test_not_equals() {
+ var identity = identityOf(new TestRecord(10));
+ var identity2 = identityOf(new TestRecord(10));
+ assertNotEquals(identity, identity2);
+ assertEquals(identity.value(), identity2.value());
+ }
+
+ @Test
+ public void test_Foo() {
+ var foo = new Foo(10);
+ assertFalse(foo.accessed());
+
+ foo.hashCode();
+ assertTrue(foo.accessed());
+ assertTrue(foo.hashCodeCalled());
+ assertFalse(foo.equalsCalled());
+
+ foo = new Foo(1);
+ foo.equals(null);
+ assertTrue(foo.accessed());
+ assertFalse(foo.hashCodeCalled());
+ assertTrue(foo.equalsCalled());
+ }
+
+ @Test
+ public void test_wrappedValue_not_accessed() {
+ var identity = identityOf(new Foo(10));
+ var identity2 = identityOf(new Foo(10));
+ assertNotEquals(identity, identity2);
+
+ assertFalse(identity.value().accessed());
+ assertFalse(identity2.value().accessed());
+
+ assertEquals(identity.value(), identity2.value());
+ assertEquals(identity2.value(), identity.value());
+
+ assertTrue(identity.value().accessed());
+ assertTrue(identity2.value().accessed());
+ }
+
+ @Test
+ public void test_wrappedValue_not_accessed_in_set() {
+ var identitySet = Set.of(identityOf(new Foo(10)), identityOf(new Foo(10)), identityOf(new Foo(10)));
+ assertEquals(3, identitySet.size());
+
+ var valueSet = identitySet.stream().peek(identity -> {
+ assertFalse(identity.value().accessed());
+ }).map(IdentityWrapper::value).collect(Collectors.toSet());
+
+ assertEquals(1, valueSet.size());
+ }
+
+ private static IdentityWrapper identityOf(T obj) {
+ return new IdentityWrapper<>(obj);
+ }
+
+ private record TestRecord(int v) {}
+
+ private final static class Foo {
+
+ Foo(int v) {
+ this.v = v;
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return Objects.hash(v);
+ } finally {
+ hashCodeCalled = true;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ try {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Foo other = (Foo) obj;
+ return v == other.v;
+ } finally {
+ equalsCalled = true;
+ }
+ }
+
+ boolean equalsCalled() {
+ return equalsCalled;
+ }
+
+ boolean hashCodeCalled() {
+ return hashCodeCalled;
+ }
+
+ boolean accessed() {
+ return equalsCalled() || hashCodeCalled();
+ }
+
+ private final int v;
+ private boolean equalsCalled;
+ private boolean hashCodeCalled;
+ }
+}
diff --git a/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java
new file mode 100644
index 0000000000000..c91b178cb108b
--- /dev/null
+++ b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2025, 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.Map;
+import java.util.Objects;
+import org.junit.jupiter.api.Assertions;
+
+
+public final class JUnitUtils {
+
+ /**
+ * Convenience adapter for {@link Assertions#assertArrayEquals(byte[], byte[])},
+ * {@link Assertions#assertArrayEquals(int[], int[])},
+ * {@link Assertions#assertArrayEquals(Object[], Object[])}, etc. methods.
+ *
+ * @param expected the expected array to test for equality
+ * @param actual the actual array to test for equality
+ */
+ public static void assertArrayEquals(Object expected, Object actual) {
+ ARRAY_ASSERTERS.getOrDefault(expected.getClass().componentType(), OBJECT_ARRAY_ASSERTER).acceptUnchecked(expected, actual);
+ }
+
+ /**
+ * Converts the given exception object to a property map.
+ *
+ * Values returned by public getters are added to the map. Names of getters are
+ * the keys in the returned map. The values are property map representations of
+ * the objects returned by the getters. Only {@link Throwable#getMessage()} and
+ * {@link Throwable#getCause()} getters are picked for the property map by
+ * default. If the exception class has additional getters, they will be added to
+ * the map. {@code null} is permitted.
+ *
+ * @param ex the exception to convert into a property map
+ * @return the property map view of the given exception object
+ */
+ public static Map exceptionAsPropertyMap(Exception ex) {
+ return EXCEPTION_OM.toMap(ex);
+ }
+
+
+ public static final class ExceptionPattern {
+
+ public ExceptionPattern() {
+ }
+
+ public boolean match(Exception ex) {
+ Objects.requireNonNull(ex);
+
+ if (expectedType != null && !expectedType.isInstance(ex)) {
+ return false;
+ }
+
+ if (expectedMessage != null && !expectedMessage.equals(ex.getMessage())) {
+ return false;
+ }
+
+ if (expectedCauseType != null && !expectedCauseType.isInstance(ex.getCause())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public ExceptionPattern hasMessage(String v) {
+ expectedMessage = v;
+ return this;
+ }
+
+ public ExceptionPattern isInstanceOf(Class extends Exception> v) {
+ expectedType = v;
+ return this;
+ }
+
+ public ExceptionPattern isCauseInstanceOf(Class extends Throwable> v) {
+ expectedCauseType = v;
+ return this;
+ }
+
+ public ExceptionPattern hasCause(boolean v) {
+ return isCauseInstanceOf(v ? Exception.class : null);
+ }
+
+ public ExceptionPattern hasCause() {
+ return hasCause(true);
+ }
+
+ private String expectedMessage;
+ private Class extends Exception> expectedType;
+ private Class extends Throwable> expectedCauseType;
+ }
+
+
+ @FunctionalInterface
+ private interface ArrayEqualsAsserter {
+ void accept(T expected, T actual);
+
+ @SuppressWarnings("unchecked")
+ default void acceptUnchecked(Object expected, Object actual) {
+ accept((T)expected, (T)actual);
+ }
+ }
+
+
+ private static final Map, ArrayEqualsAsserter>> ARRAY_ASSERTERS = Map.of(
+ boolean.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ byte.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ char.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ double.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ float.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ int.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ long.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals,
+ short.class, (ArrayEqualsAsserter)Assertions::assertArrayEquals
+ );
+
+ private static final ArrayEqualsAsserter OBJECT_ARRAY_ASSERTER = Assertions::assertArrayEquals;
+
+ private static final ObjectMapper EXCEPTION_OM = ObjectMapper.standard().create();
+}
diff --git a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java
index 4d3b33bcd6b58..8d373cb2b86dc 100644
--- a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java
+++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java
@@ -164,7 +164,7 @@ public static void testDesktopFileFromResourceDir() throws IOException {
"Exec=APPLICATION_LAUNCHER",
"Terminal=false",
"Type=Application",
- "Comment=",
+ "Comment=APPLICATION_DESCRIPTION",
"Icon=APPLICATION_ICON",
"Categories=DEPLOY_BUNDLE_CATEGORY",
expectedVersionString
diff --git a/test/jdk/tools/jpackage/share/AddLShortcutTest.java b/test/jdk/tools/jpackage/share/AddLShortcutTest.java
index 9c50c6ffc98ca..f000e79227e76 100644
--- a/test/jdk/tools/jpackage/share/AddLShortcutTest.java
+++ b/test/jdk/tools/jpackage/share/AddLShortcutTest.java
@@ -118,6 +118,12 @@ public void test() {
HelloApp.createBundle(JavaAppDesc.parse(addLauncherApp + "*another.jar:Welcome"), cmd.inputDir());
});
+ if (RunnablePackageTest.hasAction(RunnablePackageTest.Action.INSTALL)) {
+ // Ensure launchers are executable because the output bundle will be installed
+ // and launchers will be attempted to be executed through their shortcuts.
+ packageTest.addInitializer(JPackageCommand::ignoreFakeRuntime);
+ }
+
new FileAssociations(packageName).applyTo(packageTest);
new AdditionalLauncher("Foo")
diff --git a/test/jdk/tools/jpackage/share/AddLauncherTest.java b/test/jdk/tools/jpackage/share/AddLauncherTest.java
index a7bfbf376edb8..21f475cbd7810 100644
--- a/test/jdk/tools/jpackage/share/AddLauncherTest.java
+++ b/test/jdk/tools/jpackage/share/AddLauncherTest.java
@@ -21,18 +21,22 @@
* questions.
*/
-import java.nio.file.Path;
-import java.util.Map;
import java.lang.invoke.MethodHandles;
-import jdk.jpackage.test.PackageTest;
-import jdk.jpackage.test.FileAssociations;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import jdk.internal.util.OperatingSystem;
import jdk.jpackage.test.AdditionalLauncher;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.CfgFile;
+import jdk.jpackage.test.ConfigurationTarget;
+import jdk.jpackage.test.FileAssociations;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JavaAppDesc;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.RunnablePackageTest.Action;
import jdk.jpackage.test.TKit;
-import jdk.jpackage.test.Annotations.Test;
-import jdk.jpackage.test.Annotations.Parameter;
-import jdk.jpackage.test.CfgFile;
/**
* Test --add-launcher parameter. Output of the test should be
@@ -233,6 +237,61 @@ public void testMainLauncherIsModular(boolean mainLauncherIsModular) {
"Check app.classpath value in ModularAppLauncher cfg file");
}
+ /**
+ * Test --description option
+ */
+ @Test(ifNotOS = OperatingSystem.MACOS) // Don't run on macOS as launcher description is ignored on this platform
+ @Parameter("true")
+ @Parameter("fase")
+ public void testDescription(boolean withPredefinedAppImage) {
+
+ ConfigurationTarget target;
+ if (TKit.isWindows() || withPredefinedAppImage) {
+ target = new ConfigurationTarget(JPackageCommand.helloAppImage());
+ } else {
+ target = new ConfigurationTarget(new PackageTest().configureHelloApp());
+ }
+
+ target.addInitializer(cmd -> {
+ cmd.setArgumentValue("--name", "Foo").setArgumentValue("--description", "Hello");
+ cmd.setFakeRuntime();
+ cmd.setStandardAsserts(JPackageCommand.StandardAssert.MAIN_LAUNCHER_DESCRIPTION);
+ });
+
+ target.add(new AdditionalLauncher("x"));
+ target.add(new AdditionalLauncher("bye").setProperty("description", "Bye"));
+
+ target.test().ifPresent(test -> {
+ // Make all launchers have shortcuts and thus .desktop files.
+ // Launcher description is recorded in a desktop file and verified automatically.
+ test.mutate(addLinuxShortcuts());
+ });
+
+ target.cmd().ifPresent(withPredefinedAppImage ? JPackageCommand::execute : JPackageCommand::executeAndAssertImageCreated);
+ target.test().ifPresent(test -> {
+ test.run(Action.CREATE_AND_UNPACK);
+ });
+
+ if (withPredefinedAppImage) {
+ new PackageTest().addInitializer(cmd -> {
+ cmd.setArgumentValue("--name", "Bar");
+ // Should not have impact of launcher descriptions, but it does.
+ cmd.setArgumentValue("--description", "Installer");
+ cmd.removeArgumentWithValue("--input").setArgumentValue("--app-image", target.cmd().orElseThrow().outputBundle());
+ }).mutate(addLinuxShortcuts()).run(Action.CREATE_AND_UNPACK);
+ }
+ }
+
+ private static Consumer addLinuxShortcuts() {
+ return test -> {
+ test.forTypes(PackageType.LINUX, () -> {
+ test.addInitializer(cmd -> {
+ cmd.addArgument("--linux-shortcut");
+ });
+ });
+ };
+ }
+
private static final Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
"resources", "icon" + TKit.ICON_SUFFIX));
}
diff --git a/test/jdk/tools/jpackage/share/AppImagePackageTest.java b/test/jdk/tools/jpackage/share/AppImagePackageTest.java
index 34a418c6f9ec9..aacb76b122b29 100644
--- a/test/jdk/tools/jpackage/share/AppImagePackageTest.java
+++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java
@@ -21,20 +21,21 @@
* questions.
*/
-import java.nio.file.Path;
-import java.nio.file.Files;
import java.io.IOException;
-import java.util.List;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Predicate;
import jdk.jpackage.internal.util.XmlUtils;
-import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.CannedFormattedString;
-import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.JPackageCommand.StandardAssert;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.RunnablePackageTest.Action;
-import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.TKit;
/**
* Test --app-image parameter. The output installer should provide the same
@@ -55,56 +56,86 @@
*/
public class AppImagePackageTest {
+ /**
+ * Create a native bundle from a valid predefined app image produced by jpackage.
+ */
@Test
public static void test() {
- Path appimageOutput = TKit.workDir().resolve("appimage");
- JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
- .setArgumentValue("--dest", appimageOutput);
+ var appImageCmd = JPackageCommand.helloAppImage()
+ .setArgumentValue("--dest", TKit.createTempDirectory("appimage"));
new PackageTest()
- .addRunOnceInitializer(() -> appImageCmd.execute())
+ .addRunOnceInitializer(appImageCmd::execute)
.addInitializer(cmd -> {
cmd.addArguments("--app-image", appImageCmd.outputBundle());
cmd.removeArgumentWithValue("--input");
}).addBundleDesktopIntegrationVerifier(false).run();
}
+ /**
+ * Create a native bundle from a predefined app image not produced by jpackage
+ * but having a valid ".jpackage.xml" file.
+ *
+ * @param withIcon {@code true} if jpackage command line should have "--icon"
+ * option
+ */
@Test
@Parameter("true")
@Parameter("false")
public static void testEmpty(boolean withIcon) throws IOException {
- final String name = "EmptyAppImagePackageTest";
- final String imageName = name + (TKit.isOSX() ? ".app" : "");
- Path appImageDir = TKit.createTempDirectory("appimage").resolve(imageName);
- Files.createDirectories(appImageDir.resolve("bin"));
- Path libDir = Files.createDirectories(appImageDir.resolve("lib"));
- TKit.createTextFile(libDir.resolve("README"),
- List.of("This is some arbitrary text for the README file\n"));
+ var appImageCmd = JPackageCommand.helloAppImage()
+ .setFakeRuntime()
+ .setArgumentValue("--name", "EmptyAppImagePackageTest")
+ .setArgumentValue("--dest", TKit.createTempDirectory("appimage"));
new PackageTest()
+ .addRunOnceInitializer(appImageCmd::execute)
+ .addRunOnceInitializer(() -> {
+ var layout = appImageCmd.appLayout();
+ if (!TKit.isOSX()) {
+ // Delete the launcher if not on macOS.
+ // On macOS, deleting the launcher will render the app bundle invalid.
+ TKit.deleteIfExists(appImageCmd.appLauncherPath());
+ }
+ // Delete the runtime.
+ TKit.deleteDirectoryRecursive(layout.runtimeDirectory());
+ // Delete the "app" dir.
+ TKit.deleteDirectoryRecursive(layout.appDirectory());
+
+ new AppImageFile(appImageCmd.name(), "PhonyMainClass").save(appImageCmd.outputBundle());
+ var appImageDir = appImageCmd.outputBundle();
+
+ TKit.trace(String.format("Files in [%s] app image:", appImageDir));
+ try (var files = Files.walk(appImageDir)) {
+ files.sequential()
+ .filter(Predicate.isEqual(appImageDir).negate())
+ .map(path -> String.format("[%s]", appImageDir.relativize(path)))
+ .forEachOrdered(TKit::trace);
+ TKit.trace("Done");
+ }
+ })
.addInitializer(cmd -> {
- cmd.addArguments("--app-image", appImageDir);
+ cmd.addArguments("--app-image", appImageCmd.outputBundle());
if (withIcon) {
cmd.addArguments("--icon", iconPath("icon"));
}
cmd.removeArgumentWithValue("--input");
- new AppImageFile("EmptyAppImagePackageTest", "Hello").save(appImageDir);
- // on mac, with --app-image and without --mac-package-identifier,
- // will try to infer it from the image, so foreign image needs it.
- if (TKit.isOSX()) {
- cmd.addArguments("--mac-package-identifier", name);
- }
+ cmd.excludeStandardAsserts(
+ StandardAssert.MAIN_JAR_FILE,
+ StandardAssert.MAIN_LAUNCHER_FILES,
+ StandardAssert.MAC_BUNDLE_STRUCTURE,
+ StandardAssert.RUNTIME_DIRECTORY);
})
- // On macOS we always signing app image and signing will fail, since
- // test produces invalid app bundle.
- .setExpectedExitCode(TKit.isOSX() ? 1 : 0)
- .run(Action.CREATE, Action.UNPACK);
- // default: {CREATE, UNPACK, VERIFY}, but we can't verify foreign image
+ .run(Action.CREATE_AND_UNPACK);
}
+ /**
+ * Bad predefined app image - not an output of jpackage.
+ * jpackage command using the bad predefined app image doesn't have "--name" option.
+ */
@Test
public static void testBadAppImage() throws IOException {
Path appImageDir = TKit.createTempDirectory("appimage");
@@ -114,6 +145,9 @@ public static void testBadAppImage() throws IOException {
}).run(Action.CREATE);
}
+ /**
+ * Bad predefined app image - not an output of jpackage.
+ */
@Test
public static void testBadAppImage2() throws IOException {
Path appImageDir = TKit.createTempDirectory("appimage");
@@ -121,8 +155,11 @@ public static void testBadAppImage2() throws IOException {
configureBadAppImage(appImageDir).run(Action.CREATE);
}
+ /**
+ * Bad predefined app image - valid app image missing ".jpackage.xml" file.
+ */
@Test
- public static void testBadAppImage3() throws IOException {
+ public static void testBadAppImage3() {
Path appImageDir = TKit.createTempDirectory("appimage");
JPackageCommand appImageCmd = JPackageCommand.helloAppImage().
@@ -134,8 +171,11 @@ public static void testBadAppImage3() throws IOException {
}).run(Action.CREATE);
}
+ /**
+ * Bad predefined app image - valid app image with invalid ".jpackage.xml" file.
+ */
@Test
- public static void testBadAppImageFile() throws IOException {
+ public static void testBadAppImageFile() {
final var appImageRoot = TKit.createTempDirectory("appimage");
final var appImageCmd = JPackageCommand.helloAppImage().
diff --git a/test/jdk/tools/jpackage/share/InOutPathTest.java b/test/jdk/tools/jpackage/share/InOutPathTest.java
index f7c597d2ed346..d36731c2960e5 100644
--- a/test/jdk/tools/jpackage/share/InOutPathTest.java
+++ b/test/jdk/tools/jpackage/share/InOutPathTest.java
@@ -38,7 +38,7 @@
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
import jdk.jpackage.test.JPackageCommand;
-import jdk.jpackage.test.JPackageCommand.AppLayoutAssert;
+import jdk.jpackage.test.JPackageCommand.StandardAssert;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import static jdk.jpackage.test.RunnablePackageTest.Action.CREATE_AND_UNPACK;
@@ -177,7 +177,7 @@ private static void runTest(Set packageTypes,
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);
+ cmd.excludeStandardAsserts(StandardAssert.APP_IMAGE_FILE);
}
};
diff --git a/test/jdk/tools/jpackage/share/LicenseTest.java b/test/jdk/tools/jpackage/share/LicenseTest.java
index c9e3c8508aa61..1c6bfd51b62d6 100644
--- a/test/jdk/tools/jpackage/share/LicenseTest.java
+++ b/test/jdk/tools/jpackage/share/LicenseTest.java
@@ -208,7 +208,7 @@ private static Path linuxLicenseFile(JPackageCommand cmd) {
private static void verifyLicenseFileInLinuxPackage(JPackageCommand cmd,
Path expectedLicensePath) {
TKit.assertTrue(LinuxHelper.getPackageFiles(cmd).filter(path -> path.equals(
- expectedLicensePath)).findFirst().orElse(null) != null,
+ expectedLicensePath)).findFirst().isPresent(),
String.format("Check license file [%s] is in %s package",
expectedLicensePath, LinuxHelper.getPackageName(cmd)));
}
diff --git a/test/jdk/tools/jpackage/share/RuntimePackageTest.java b/test/jdk/tools/jpackage/share/RuntimePackageTest.java
index f66f774b227ac..caa129713b48b 100644
--- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java
@@ -135,11 +135,7 @@ private static PackageTest init(ThrowingSupplier createRuntime) {
})
.addInstallVerifier(cmd -> {
var src = TKit.assertDirectoryContentRecursive(inputRuntimeDir(cmd)).items();
- Path dest = cmd.appRuntimeDirectory();
- if (TKit.isOSX()) {
- dest = dest.resolve("Contents/Home");
- }
-
+ var dest = cmd.appLayout().runtimeHomeDirectory();
TKit.assertDirectoryContentRecursive(dest).match(src);
})
.forTypes(PackageType.LINUX_DEB, test -> {