8343876: Enhancements to jpackage test lib
Reviewed-by: almatvee
This commit is contained in:
parent
aa10ec7c96
commit
41a627b789
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* 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 static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import static jdk.internal.util.OperatingSystem.LINUX;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import static jdk.jpackage.test.Functional.ThrowingSupplier.toSupplier;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test jpackage test library's annotation processor
|
||||
* @library /test/jdk/tools/jpackage/helpers
|
||||
* @build jdk.jpackage.test.*
|
||||
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.AnnotationsTest
|
||||
*/
|
||||
public class AnnotationsTest {
|
||||
|
||||
public static void main(String... args) {
|
||||
runTests(BasicTest.class, ParameterizedInstanceTest.class);
|
||||
for (var os : OperatingSystem.values()) {
|
||||
try {
|
||||
TestBuilderConfig.setOperatingSystem(os);
|
||||
TKit.log("Current operating system: " + os);
|
||||
runTests(IfOSTest.class);
|
||||
} finally {
|
||||
TestBuilderConfig.setDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class BasicTest extends TestExecutionRecorder {
|
||||
@Test
|
||||
public void testNoArg() {
|
||||
recordTestCase();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter("TRUE")
|
||||
public int testNoArg(boolean v) {
|
||||
recordTestCase(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter({})
|
||||
@Parameter("a")
|
||||
@Parameter({"b", "c"})
|
||||
public void testVarArg(Path ... paths) {
|
||||
recordTestCase((Object[]) paths);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter({"12", "foo"})
|
||||
@Parameter({"-89", "bar", "more"})
|
||||
@Parameter({"-89", "bar", "more", "moore"})
|
||||
public void testVarArg2(int a, String b, String ... other) {
|
||||
recordTestCase(a, b, other);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterSupplier("dateSupplier")
|
||||
@ParameterSupplier("jdk.jpackage.test.AnnotationsTest.dateSupplier")
|
||||
public void testDates(LocalDate v) {
|
||||
recordTestCase(v);
|
||||
}
|
||||
|
||||
public static Set<String> getExpectedTestDescs() {
|
||||
return Set.of(
|
||||
"().testNoArg()",
|
||||
"().testNoArg(true)",
|
||||
"().testVarArg()",
|
||||
"().testVarArg(a)",
|
||||
"().testVarArg(b, c)",
|
||||
"().testVarArg2(-89, bar, [more, moore](length=2))",
|
||||
"().testVarArg2(-89, bar, [more](length=1))",
|
||||
"().testVarArg2(12, foo, [](length=0))",
|
||||
"().testDates(2018-05-05)",
|
||||
"().testDates(2018-07-11)",
|
||||
"().testDates(2034-05-05)",
|
||||
"().testDates(2056-07-11)"
|
||||
);
|
||||
}
|
||||
|
||||
public static Collection<Object[]> dateSupplier() {
|
||||
return List.of(new Object[][] {
|
||||
{ LocalDate.parse("2018-05-05") },
|
||||
{ LocalDate.parse("2018-07-11") },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParameterizedInstanceTest extends TestExecutionRecorder {
|
||||
public ParameterizedInstanceTest(String... args) {
|
||||
super((Object[]) args);
|
||||
}
|
||||
|
||||
public ParameterizedInstanceTest(int o) {
|
||||
super(o);
|
||||
}
|
||||
|
||||
public ParameterizedInstanceTest(int a, Boolean[] b, String c, String ... other) {
|
||||
super(a, b, c, other);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArgs() {
|
||||
recordTestCase();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterSupplier("jdk.jpackage.test.AnnotationsTest.dateSupplier")
|
||||
public void testDates(LocalDate v) {
|
||||
recordTestCase(v);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter("a")
|
||||
public static void staticTest(String arg) {
|
||||
staticRecorder.recordTestCase(arg);
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> input() {
|
||||
return List.of(new Object[][] {
|
||||
{},
|
||||
{55, new Boolean[]{false, true, false}, "foo", "bar"},
|
||||
{78},
|
||||
});
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> input2() {
|
||||
return List.of(new Object[][] {
|
||||
{51, new boolean[]{true, true, true}, "foo"},
|
||||
{33},
|
||||
{55, null, null },
|
||||
{55, null, null, "1" },
|
||||
});
|
||||
}
|
||||
|
||||
public static Set<String> getExpectedTestDescs() {
|
||||
return Set.of(
|
||||
"().testNoArgs()",
|
||||
"(33).testNoArgs()",
|
||||
"(78).testNoArgs()",
|
||||
"(55, [false, true, false](length=3), foo, [bar](length=1)).testNoArgs()",
|
||||
"(51, [true, true, true](length=3), foo, [](length=0)).testNoArgs()",
|
||||
"().testDates(2034-05-05)",
|
||||
"().testDates(2056-07-11)",
|
||||
"(33).testDates(2034-05-05)",
|
||||
"(33).testDates(2056-07-11)",
|
||||
"(51, [true, true, true](length=3), foo, [](length=0)).testDates(2034-05-05)",
|
||||
"(51, [true, true, true](length=3), foo, [](length=0)).testDates(2056-07-11)",
|
||||
"(55, [false, true, false](length=3), foo, [bar](length=1)).testDates(2034-05-05)",
|
||||
"(55, [false, true, false](length=3), foo, [bar](length=1)).testDates(2056-07-11)",
|
||||
"(78).testDates(2034-05-05)",
|
||||
"(78).testDates(2056-07-11)",
|
||||
"(55, null, null, [1](length=1)).testDates(2034-05-05)",
|
||||
"(55, null, null, [1](length=1)).testDates(2056-07-11)",
|
||||
"(55, null, null, [1](length=1)).testNoArgs()",
|
||||
"(55, null, null, [](length=0)).testDates(2034-05-05)",
|
||||
"(55, null, null, [](length=0)).testDates(2056-07-11)",
|
||||
"(55, null, null, [](length=0)).testNoArgs()",
|
||||
"().staticTest(a)"
|
||||
);
|
||||
}
|
||||
|
||||
private final static TestExecutionRecorder staticRecorder = new TestExecutionRecorder(ParameterizedInstanceTest.class);
|
||||
}
|
||||
|
||||
public static class IfOSTest extends TestExecutionRecorder {
|
||||
public IfOSTest(int a, String b) {
|
||||
super(a, b);
|
||||
}
|
||||
|
||||
@Test(ifOS = OperatingSystem.LINUX)
|
||||
public void testNoArgs() {
|
||||
recordTestCase();
|
||||
}
|
||||
|
||||
@Test(ifNotOS = OperatingSystem.LINUX)
|
||||
public void testNoArgs2() {
|
||||
recordTestCase();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameter(value = "foo", ifOS = OperatingSystem.LINUX)
|
||||
@Parameter(value = {"foo", "bar"}, ifOS = { OperatingSystem.LINUX, OperatingSystem.MACOS })
|
||||
@Parameter(value = {}, ifNotOS = { OperatingSystem.WINDOWS })
|
||||
public void testVarArgs(String ... args) {
|
||||
recordTestCase((Object[]) args);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ParameterSupplier(value = "jdk.jpackage.test.AnnotationsTest.dateSupplier", ifOS = OperatingSystem.WINDOWS)
|
||||
public void testDates(LocalDate v) {
|
||||
recordTestCase(v);
|
||||
}
|
||||
|
||||
@Parameters(ifOS = OperatingSystem.LINUX)
|
||||
public static Collection<Object[]> input() {
|
||||
return Set.of(new Object[][] {
|
||||
{7, null},
|
||||
});
|
||||
}
|
||||
|
||||
@Parameters(ifNotOS = {OperatingSystem.LINUX, OperatingSystem.MACOS})
|
||||
public static Collection<Object[]> input2() {
|
||||
return Set.of(new Object[][] {
|
||||
{10, "hello"},
|
||||
});
|
||||
}
|
||||
|
||||
@Parameters(ifNotOS = OperatingSystem.LINUX)
|
||||
public static Collection<Object[]> input3() {
|
||||
return Set.of(new Object[][] {
|
||||
{15, "bye"},
|
||||
});
|
||||
}
|
||||
|
||||
public static Set<String> getExpectedTestDescs() {
|
||||
switch (TestBuilderConfig.getDefault().getOperatingSystem()) {
|
||||
case LINUX -> {
|
||||
return Set.of(
|
||||
"(7, null).testNoArgs()",
|
||||
"(7, null).testVarArgs()",
|
||||
"(7, null).testVarArgs(foo)",
|
||||
"(7, null).testVarArgs(foo, bar)"
|
||||
);
|
||||
}
|
||||
|
||||
case MACOS -> {
|
||||
return Set.of(
|
||||
"(15, bye).testNoArgs2()",
|
||||
"(15, bye).testVarArgs()",
|
||||
"(15, bye).testVarArgs(foo, bar)"
|
||||
);
|
||||
}
|
||||
|
||||
case WINDOWS -> {
|
||||
return Set.of(
|
||||
"(15, bye).testDates(2034-05-05)",
|
||||
"(15, bye).testDates(2056-07-11)",
|
||||
"(15, bye).testNoArgs2()",
|
||||
"(10, hello).testDates(2034-05-05)",
|
||||
"(10, hello).testDates(2056-07-11)",
|
||||
"(10, hello).testNoArgs2()"
|
||||
);
|
||||
}
|
||||
|
||||
case AIX -> {
|
||||
return Set.of(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Object[]> dateSupplier() {
|
||||
return List.of(new Object[][] {
|
||||
{ LocalDate.parse("2034-05-05") },
|
||||
{ LocalDate.parse("2056-07-11") },
|
||||
});
|
||||
}
|
||||
|
||||
private static void runTests(Class<? extends TestExecutionRecorder>... tests) {
|
||||
ACTUAL_TEST_DESCS.get().clear();
|
||||
|
||||
var expectedTestDescs = Stream.of(tests)
|
||||
.map(AnnotationsTest::getExpectedTestDescs)
|
||||
.flatMap(x -> x)
|
||||
// Collect in the map to check for collisions for free
|
||||
.collect(toMap(x -> x, x -> ""))
|
||||
.keySet();
|
||||
|
||||
var args = Stream.of(tests).map(test -> {
|
||||
return String.format("--jpt-run=%s", test.getName());
|
||||
}).toArray(String[]::new);
|
||||
|
||||
try {
|
||||
Main.main(args);
|
||||
assertRecordedTestDescs(expectedTestDescs);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<String> getExpectedTestDescs(Class<?> type) {
|
||||
return toSupplier(() -> {
|
||||
var method = type.getMethod("getExpectedTestDescs");
|
||||
var testDescPefix = type.getName();
|
||||
return ((Set<String>)method.invoke(null)).stream().map(desc -> {
|
||||
return testDescPefix + desc;
|
||||
});
|
||||
}).get();
|
||||
}
|
||||
|
||||
private static void assertRecordedTestDescs(Set<String> expectedTestDescs) {
|
||||
var comm = Comm.compare(expectedTestDescs, ACTUAL_TEST_DESCS.get());
|
||||
if (!comm.unique1().isEmpty()) {
|
||||
System.err.println("Missing test case signatures:");
|
||||
comm.unique1().stream().sorted().sequential().forEachOrdered(System.err::println);
|
||||
System.err.println("<>");
|
||||
}
|
||||
|
||||
if (!comm.unique2().isEmpty()) {
|
||||
System.err.println("Unexpected test case signatures:");
|
||||
comm.unique2().stream().sorted().sequential().forEachOrdered(System.err::println);
|
||||
System.err.println("<>");
|
||||
}
|
||||
|
||||
if (!comm.unique2().isEmpty() || !comm.unique1().isEmpty()) {
|
||||
// Don't use TKit asserts as this call is outside the test execution
|
||||
throw new AssertionError("Test case signatures mismatched");
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestExecutionRecorder {
|
||||
protected TestExecutionRecorder(Object ... args) {
|
||||
this.testClass = getClass();
|
||||
this.testDescBuilder = TestInstance.TestDesc.createBuilder().ctorArgs(args);
|
||||
}
|
||||
|
||||
TestExecutionRecorder(Class<?> testClass) {
|
||||
this.testClass = testClass;
|
||||
this.testDescBuilder = TestInstance.TestDesc.createBuilder().ctorArgs();
|
||||
}
|
||||
|
||||
protected void recordTestCase(Object ... args) {
|
||||
testDescBuilder.methodArgs(args).method(getCurrentTestCase());
|
||||
var testCaseDescs = ACTUAL_TEST_DESCS.get();
|
||||
var testCaseDesc = testDescBuilder.get().testFullName();
|
||||
TKit.assertTrue(!testCaseDescs.contains(testCaseDesc), String.format(
|
||||
"Check this test case is executed for the first time",
|
||||
testCaseDesc));
|
||||
TKit.assertTrue(!executed, "Check this test case instance is not reused");
|
||||
executed = true;
|
||||
testCaseDescs.add(testCaseDesc);
|
||||
}
|
||||
|
||||
private Method getCurrentTestCase() {
|
||||
return StackWalker.getInstance(RETAIN_CLASS_REFERENCE).walk(frames -> {
|
||||
return frames.map(frame -> {
|
||||
var methodType = frame.getMethodType();
|
||||
var methodName = frame.getMethodName();
|
||||
var methodReturn = methodType.returnType();
|
||||
var methodParameters = methodType.parameterArray();
|
||||
return Stream.of(testClass.getDeclaredMethods()).filter(method -> {
|
||||
return method.getName().equals(methodName)
|
||||
&& method.getReturnType().equals(methodReturn)
|
||||
&& Arrays.equals(method.getParameterTypes(), methodParameters)
|
||||
&& method.isAnnotationPresent(Test.class);
|
||||
}).findFirst();
|
||||
}).dropWhile(Optional::isEmpty).map(Optional::get).findFirst();
|
||||
}).get();
|
||||
}
|
||||
|
||||
private boolean executed;
|
||||
private final TestInstance.TestDesc.Builder testDescBuilder;
|
||||
private final Class<?> testClass;
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Set<String>> ACTUAL_TEST_DESCS = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Set<String> initialValue() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
};
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
@ -27,6 +27,7 @@ import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
|
||||
public class Annotations {
|
||||
|
||||
@ -43,6 +44,14 @@ public class Annotations {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Test {
|
||||
|
||||
OperatingSystem[] ifOS() default {
|
||||
OperatingSystem.LINUX,
|
||||
OperatingSystem.WINDOWS,
|
||||
OperatingSystem.MACOS
|
||||
};
|
||||
|
||||
OperatingSystem[] ifNotOS() default {};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ -51,6 +60,14 @@ public class Annotations {
|
||||
public @interface Parameter {
|
||||
|
||||
String[] value();
|
||||
|
||||
OperatingSystem[] ifOS() default {
|
||||
OperatingSystem.LINUX,
|
||||
OperatingSystem.WINDOWS,
|
||||
OperatingSystem.MACOS
|
||||
};
|
||||
|
||||
OperatingSystem[] ifNotOS() default {};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ -60,8 +77,39 @@ public class Annotations {
|
||||
Parameter[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Repeatable(ParameterSupplierGroup.class)
|
||||
public @interface ParameterSupplier {
|
||||
|
||||
String value();
|
||||
|
||||
OperatingSystem[] ifOS() default {
|
||||
OperatingSystem.LINUX,
|
||||
OperatingSystem.WINDOWS,
|
||||
OperatingSystem.MACOS
|
||||
};
|
||||
|
||||
OperatingSystem[] ifNotOS() default {};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface ParameterSupplierGroup {
|
||||
|
||||
ParameterSupplier[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Parameters {
|
||||
|
||||
OperatingSystem[] ifOS() default {
|
||||
OperatingSystem.LINUX,
|
||||
OperatingSystem.WINDOWS,
|
||||
OperatingSystem.MACOS
|
||||
};
|
||||
|
||||
OperatingSystem[] ifNotOS() default {};
|
||||
}
|
||||
}
|
||||
|
39
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Comm.java
Normal file
39
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Comm.java
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
record Comm<T>(Set<T> common, Set<T> unique1, Set<T> unique2) {
|
||||
|
||||
static <T> Comm<T> compare(Set<T> a, Set<T> b) {
|
||||
Set<T> common = new HashSet<>(a);
|
||||
common.retainAll(b);
|
||||
Set<T> unique1 = new HashSet<>(a);
|
||||
unique1.removeAll(common);
|
||||
Set<T> unique2 = new HashSet<>(b);
|
||||
unique2.removeAll(common);
|
||||
return new Comm(common, unique1, unique2);
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -40,6 +40,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.ApplicationLayout;
|
||||
import jdk.jpackage.internal.IOUtils;
|
||||
import jdk.jpackage.test.Functional.ThrowingConsumer;
|
||||
import jdk.jpackage.test.PackageTest.PackageHandlers;
|
||||
@ -399,6 +400,15 @@ public final class LinuxHelper {
|
||||
private static void verifyDesktopFile(JPackageCommand cmd, Path desktopFile)
|
||||
throws IOException {
|
||||
TKit.trace(String.format("Check [%s] file BEGIN", desktopFile));
|
||||
|
||||
var launcherName = Stream.of(List.of(cmd.name()), cmd.addLauncherNames()).flatMap(List::stream).filter(name -> {
|
||||
return getDesktopFile(cmd, name).equals(desktopFile);
|
||||
}).findAny();
|
||||
if (!cmd.hasArgument("--app-image")) {
|
||||
TKit.assertTrue(launcherName.isPresent(),
|
||||
"Check the desktop file corresponds to one of app launchers");
|
||||
}
|
||||
|
||||
List<String> lines = Files.readAllLines(desktopFile);
|
||||
TKit.assertEquals("[Desktop Entry]", lines.get(0), "Check file header");
|
||||
|
||||
@ -428,7 +438,7 @@ public final class LinuxHelper {
|
||||
"Check value of [%s] key", key));
|
||||
}
|
||||
|
||||
// Verify value of `Exec` property in .desktop files are escaped if required
|
||||
// Verify the value of `Exec` key is escaped if required
|
||||
String launcherPath = data.get("Exec");
|
||||
if (Pattern.compile("\\s").matcher(launcherPath).find()) {
|
||||
TKit.assertTrue(launcherPath.startsWith("\"")
|
||||
@ -437,10 +447,25 @@ public final class LinuxHelper {
|
||||
launcherPath = launcherPath.substring(1, launcherPath.length() - 1);
|
||||
}
|
||||
|
||||
Stream.of(launcherPath, data.get("Icon"))
|
||||
.map(Path::of)
|
||||
.map(cmd::pathToUnpackedPackageFile)
|
||||
.forEach(TKit::assertFileExists);
|
||||
if (launcherName.isPresent()) {
|
||||
TKit.assertEquals(launcherPath, cmd.pathToPackageFile(
|
||||
cmd.appLauncherPath(launcherName.get())).toString(),
|
||||
String.format(
|
||||
"Check the value of [Exec] key references [%s] app launcher",
|
||||
launcherName.get()));
|
||||
}
|
||||
|
||||
for (var e : List.<Map.Entry<Map.Entry<String, Optional<String>>, Function<ApplicationLayout, Path>>>of(
|
||||
Map.entry(Map.entry("Exec", Optional.of(launcherPath)), ApplicationLayout::launchersDirectory),
|
||||
Map.entry(Map.entry("Icon", Optional.empty()), ApplicationLayout::destktopIntegrationDirectory))) {
|
||||
var path = e.getKey().getValue().or(() -> Optional.of(data.get(
|
||||
e.getKey().getKey()))).map(Path::of).get();
|
||||
TKit.assertFileExists(cmd.pathToUnpackedPackageFile(path));
|
||||
Path expectedDir = cmd.pathToPackageFile(e.getValue().apply(cmd.appLayout()));
|
||||
TKit.assertTrue(path.getParent().equals(expectedDir), String.format(
|
||||
"Check the value of [%s] key references a file in [%s] folder",
|
||||
e.getKey().getKey(), expectedDir));
|
||||
}
|
||||
|
||||
TKit.trace(String.format("Check [%s] file END", desktopFile));
|
||||
}
|
||||
@ -725,10 +750,10 @@ public final class LinuxHelper {
|
||||
|
||||
private static Map<PackageType, String> archs;
|
||||
|
||||
private final static Pattern XDG_CMD_ICON_SIZE_PATTERN = Pattern.compile("\\s--size\\s+(\\d+)\\b");
|
||||
private static final Pattern XDG_CMD_ICON_SIZE_PATTERN = Pattern.compile("\\s--size\\s+(\\d+)\\b");
|
||||
|
||||
// Values grabbed from https://linux.die.net/man/1/xdg-icon-resource
|
||||
private final static Set<Integer> XDG_CMD_VALID_ICON_SIZES = Set.of(16, 22, 32, 48, 64, 128);
|
||||
private static final Set<Integer> XDG_CMD_VALID_ICON_SIZES = Set.of(16, 22, 32, 48, 64, 128);
|
||||
|
||||
private final static Method getServiceUnitFileName = initGetServiceUnitFileName();
|
||||
private static final Method getServiceUnitFileName = initGetServiceUnitFileName();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
@ -23,11 +23,17 @@
|
||||
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import java.util.stream.Stream;
|
||||
import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
|
||||
|
||||
|
||||
@ -36,7 +42,9 @@ public final class Main {
|
||||
boolean listTests = false;
|
||||
List<TestInstance> tests = new ArrayList<>();
|
||||
try (TestBuilder testBuilder = new TestBuilder(tests::add)) {
|
||||
for (var arg : args) {
|
||||
Deque<String> argsAsList = new ArrayDeque<>(List.of(args));
|
||||
while (!argsAsList.isEmpty()) {
|
||||
var arg = argsAsList.pop();
|
||||
TestBuilder.trace(String.format("Parsing [%s]...", arg));
|
||||
|
||||
if ((CMDLINE_ARG_PREFIX + "list").equals(arg)) {
|
||||
@ -44,6 +52,29 @@ public final class Main {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith("@")) {
|
||||
// Command file
|
||||
// @=args will read arguments from the "args" file, one argument per line
|
||||
// @args will read arguments from the "args" file, splitting lines into arguments at whitespaces
|
||||
arg = arg.substring(1);
|
||||
var oneArgPerLine = arg.startsWith("=");
|
||||
if (oneArgPerLine) {
|
||||
arg = arg.substring(1);
|
||||
}
|
||||
|
||||
var newArgsStream = Files.readAllLines(Path.of(arg)).stream();
|
||||
if (!oneArgPerLine) {
|
||||
newArgsStream.map(line -> {
|
||||
return Stream.of(line.split("\\s+"));
|
||||
}).flatMap(x -> x);
|
||||
}
|
||||
|
||||
var newArgs = newArgsStream.collect(toCollection(ArrayDeque::new));
|
||||
newArgs.addAll(argsAsList);
|
||||
argsAsList = newArgs;
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
testBuilder.processCmdLineArg(arg);
|
||||
@ -62,12 +93,11 @@ public final class Main {
|
||||
|
||||
// Order tests by their full names to have stable test sequence.
|
||||
List<TestInstance> orderedTests = tests.stream()
|
||||
.sorted((a, b) -> a.fullName().compareTo(b.fullName()))
|
||||
.collect(Collectors.toList());
|
||||
.sorted(Comparator.comparing(TestInstance::fullName)).toList();
|
||||
|
||||
if (listTests) {
|
||||
// Just list the tests
|
||||
orderedTests.stream().forEach(test -> System.out.println(String.format(
|
||||
orderedTests.forEach(test -> System.out.println(String.format(
|
||||
"%s; workDir=[%s]", test.fullName(), test.workDir())));
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
@ -22,34 +22,33 @@
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.Functional.ThrowingConsumer;
|
||||
import jdk.jpackage.test.TestInstance.TestDesc;
|
||||
|
||||
class MethodCall implements ThrowingConsumer {
|
||||
|
||||
MethodCall(Object[] instanceCtorArgs, Method method) {
|
||||
this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
|
||||
DEFAULT_CTOR_ARGS);
|
||||
this.method = method;
|
||||
this.methodArgs = new Object[0];
|
||||
}
|
||||
MethodCall(Object[] instanceCtorArgs, Method method, Object ... args) {
|
||||
Objects.requireNonNull(instanceCtorArgs);
|
||||
Objects.requireNonNull(method);
|
||||
|
||||
MethodCall(Object[] instanceCtorArgs, Method method, Object arg) {
|
||||
this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
|
||||
DEFAULT_CTOR_ARGS);
|
||||
this.ctorArgs = instanceCtorArgs;
|
||||
this.method = method;
|
||||
this.methodArgs = new Object[]{arg};
|
||||
this.methodArgs = args;
|
||||
}
|
||||
|
||||
TestDesc createDescription() {
|
||||
@ -76,68 +75,35 @@ class MethodCall implements ThrowingConsumer {
|
||||
return null;
|
||||
}
|
||||
|
||||
Constructor ctor = findRequiredConstructor(method.getDeclaringClass(),
|
||||
ctorArgs);
|
||||
if (ctor.isVarArgs()) {
|
||||
// Assume constructor doesn't have fixed, only variable parameters.
|
||||
return ctor.newInstance(new Object[]{ctorArgs});
|
||||
}
|
||||
var ctor = findMatchingConstructor(method.getDeclaringClass(), ctorArgs);
|
||||
|
||||
return ctor.newInstance(ctorArgs);
|
||||
return ctor.newInstance(mapArgs(ctor, ctorArgs));
|
||||
}
|
||||
|
||||
static Object[] mapArgs(Executable executable, final Object ... args) {
|
||||
return mapPrimitiveTypeArgs(executable, mapVarArgs(executable, args));
|
||||
}
|
||||
|
||||
void checkRequiredConstructor() throws NoSuchMethodException {
|
||||
if ((method.getModifiers() & Modifier.STATIC) == 0) {
|
||||
findRequiredConstructor(method.getDeclaringClass(), ctorArgs);
|
||||
findMatchingConstructor(method.getDeclaringClass(), ctorArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private static Constructor findVarArgConstructor(Class type) {
|
||||
return Stream.of(type.getConstructors()).filter(
|
||||
Constructor::isVarArgs).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private Constructor findRequiredConstructor(Class type, Object... ctorArgs)
|
||||
private static Constructor findMatchingConstructor(Class type, Object... ctorArgs)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Supplier<NoSuchMethodException> notFoundException = () -> {
|
||||
return new NoSuchMethodException(String.format(
|
||||
var ctors = filterMatchingExecutablesForParameterValues(Stream.of(
|
||||
type.getConstructors()), ctorArgs).toList();
|
||||
|
||||
if (ctors.size() != 1) {
|
||||
// No public constructors that can handle the given arguments.
|
||||
throw new NoSuchMethodException(String.format(
|
||||
"No public contructor in %s for %s arguments", type,
|
||||
Arrays.deepToString(ctorArgs)));
|
||||
};
|
||||
|
||||
if (Stream.of(ctorArgs).allMatch(Objects::nonNull)) {
|
||||
// No `null` in constructor args, take easy path
|
||||
try {
|
||||
return type.getConstructor(Stream.of(ctorArgs).map(
|
||||
Object::getClass).collect(Collectors.toList()).toArray(
|
||||
Class[]::new));
|
||||
} catch (NoSuchMethodException ex) {
|
||||
// Failed to find ctor that can take the given arguments.
|
||||
Constructor varArgCtor = findVarArgConstructor(type);
|
||||
if (varArgCtor != null) {
|
||||
// There is one with variable number of arguments. Use it.
|
||||
return varArgCtor;
|
||||
}
|
||||
throw notFoundException.get();
|
||||
}
|
||||
}
|
||||
|
||||
List<Constructor> ctors = Stream.of(type.getConstructors())
|
||||
.filter(ctor -> ctor.getParameterCount() == ctorArgs.length)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (ctors.isEmpty()) {
|
||||
// No public constructors that can handle the given arguments.
|
||||
throw notFoundException.get();
|
||||
}
|
||||
|
||||
if (ctors.size() == 1) {
|
||||
return ctors.iterator().next();
|
||||
}
|
||||
|
||||
// Revisit this tricky case when it will start bothering.
|
||||
throw notFoundException.get();
|
||||
return ctors.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,9 +111,159 @@ class MethodCall implements ThrowingConsumer {
|
||||
method.invoke(thiz, methodArgs);
|
||||
}
|
||||
|
||||
private static Object[] mapVarArgs(Executable executable, final Object ... args) {
|
||||
if (executable.isVarArgs()) {
|
||||
var paramTypes = executable.getParameterTypes();
|
||||
Class varArgParamType = paramTypes[paramTypes.length - 1];
|
||||
|
||||
Object[] newArgs;
|
||||
if (paramTypes.length - args.length == 1) {
|
||||
// Empty var args
|
||||
|
||||
// "args" can be of type String[] if the "executable" is "foo(String ... str)"
|
||||
newArgs = Arrays.copyOf(args, args.length + 1, Object[].class);
|
||||
newArgs[newArgs.length - 1] = Array.newInstance(varArgParamType.componentType(), 0);
|
||||
} else {
|
||||
var varArgs = Arrays.copyOfRange(args, paramTypes.length - 1,
|
||||
args.length, varArgParamType);
|
||||
|
||||
// "args" can be of type String[] if the "executable" is "foo(String ... str)"
|
||||
newArgs = Arrays.copyOfRange(args, 0, paramTypes.length, Object[].class);
|
||||
newArgs[newArgs.length - 1] = varArgs;
|
||||
}
|
||||
return newArgs;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private static Object[] mapPrimitiveTypeArgs(Executable executable, final Object ... args) {
|
||||
var paramTypes = executable.getParameterTypes();
|
||||
if (paramTypes.length != args.length) {
|
||||
throw new IllegalArgumentException(
|
||||
"The number of arguments must be equal to the number of parameters of the executable");
|
||||
}
|
||||
|
||||
if (IntStream.range(0, args.length).allMatch(idx -> {
|
||||
return Optional.ofNullable(args[idx]).map(Object::getClass).map(paramTypes[idx]::isAssignableFrom).orElse(true);
|
||||
})) {
|
||||
return args;
|
||||
} else {
|
||||
final var newArgs = Arrays.copyOf(args, args.length, Object[].class);
|
||||
for (var idx = 0; idx != args.length; ++idx) {
|
||||
final var paramType = paramTypes[idx];
|
||||
final var argValue = args[idx];
|
||||
newArgs[idx] = Optional.ofNullable(argValue).map(Object::getClass).map(argType -> {
|
||||
if(argType.isArray() && !paramType.isAssignableFrom(argType)) {
|
||||
var length = Array.getLength(argValue);
|
||||
var newArray = Array.newInstance(paramType.getComponentType(), length);
|
||||
for (var arrayIdx = 0; arrayIdx != length; ++arrayIdx) {
|
||||
Array.set(newArray, arrayIdx, Array.get(argValue, arrayIdx));
|
||||
}
|
||||
return newArray;
|
||||
} else {
|
||||
return argValue;
|
||||
}
|
||||
}).orElse(argValue);
|
||||
}
|
||||
|
||||
return newArgs;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends Executable> Stream<T> filterMatchingExecutablesForParameterValues(
|
||||
Stream<T> executables, Object... args) {
|
||||
return filterMatchingExecutablesForParameterTypes(
|
||||
executables,
|
||||
Stream.of(args)
|
||||
.map(arg -> arg != null ? arg.getClass() : null)
|
||||
.toArray(Class[]::new));
|
||||
}
|
||||
|
||||
private static <T extends Executable> Stream<T> filterMatchingExecutablesForParameterTypes(
|
||||
Stream<T> executables, Class<?>... argTypes) {
|
||||
return executables.filter(executable -> {
|
||||
var parameterTypes = executable.getParameterTypes();
|
||||
|
||||
final int checkArgTypeCount;
|
||||
if (parameterTypes.length <= argTypes.length) {
|
||||
checkArgTypeCount = parameterTypes.length;
|
||||
} else if (parameterTypes.length - argTypes.length == 1 && executable.isVarArgs()) {
|
||||
// Empty optional arguments.
|
||||
checkArgTypeCount = argTypes.length;
|
||||
} else {
|
||||
// Not enough mandatory arguments.
|
||||
return false;
|
||||
}
|
||||
|
||||
var unmatched = IntStream.range(0, checkArgTypeCount).dropWhile(idx -> {
|
||||
return new ParameterTypeMatcher(parameterTypes[idx]).test(argTypes[idx]);
|
||||
}).toArray();
|
||||
|
||||
if (argTypes.length == parameterTypes.length && unmatched.length == 0) {
|
||||
// Number of argument types equals to the number of parameters
|
||||
// of the executable and all types match.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (executable.isVarArgs()) {
|
||||
var varArgType = parameterTypes[parameterTypes.length - 1].componentType();
|
||||
return IntStream.of(unmatched).allMatch(idx -> {
|
||||
return new ParameterTypeMatcher(varArgType).test(argTypes[idx]);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private static final class ParameterTypeMatcher implements Predicate<Class<?>> {
|
||||
ParameterTypeMatcher(Class<?> parameterType) {
|
||||
Objects.requireNonNull(parameterType);
|
||||
this.parameterType = NORM_TYPES.getOrDefault(parameterType, parameterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Class<?> paramaterValueType) {
|
||||
if (paramaterValueType == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
paramaterValueType = NORM_TYPES.getOrDefault(paramaterValueType, paramaterValueType);
|
||||
return parameterType.isAssignableFrom(paramaterValueType);
|
||||
}
|
||||
|
||||
private final Class<?> parameterType;
|
||||
}
|
||||
|
||||
private final Object[] methodArgs;
|
||||
private final Method method;
|
||||
private final Object[] ctorArgs;
|
||||
|
||||
final static Object[] DEFAULT_CTOR_ARGS = new Object[0];
|
||||
private static final Map<Class<?>, Class<?>> NORM_TYPES;
|
||||
|
||||
static {
|
||||
Map<Class<?>, Class<?>> primitives = Map.of(
|
||||
boolean.class, Boolean.class,
|
||||
byte.class, Byte.class,
|
||||
short.class, Short.class,
|
||||
int.class, Integer.class,
|
||||
long.class, Long.class,
|
||||
float.class, Float.class,
|
||||
double.class, Double.class);
|
||||
|
||||
Map<Class<?>, Class<?>> primitiveArrays = Map.of(
|
||||
boolean[].class, Boolean[].class,
|
||||
byte[].class, Byte[].class,
|
||||
short[].class, Short[].class,
|
||||
int[].class, Integer[].class,
|
||||
long[].class, Long[].class,
|
||||
float[].class, Float[].class,
|
||||
double[].class, Double[].class);
|
||||
|
||||
Map<Class<?>, Class<?>> combined = new HashMap<>(primitives);
|
||||
combined.putAll(primitiveArrays);
|
||||
|
||||
NORM_TYPES = Collections.unmodifiableMap(combined);
|
||||
}
|
||||
}
|
||||
|
@ -781,18 +781,18 @@ public final class TKit {
|
||||
currentTest.notifyAssert();
|
||||
|
||||
var comm = Comm.compare(content, expected);
|
||||
if (!comm.unique1.isEmpty() && !comm.unique2.isEmpty()) {
|
||||
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()) {
|
||||
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()) {
|
||||
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)));
|
||||
baseDir, format(comm.common()), format(comm.unique2())));
|
||||
} else {
|
||||
traceAssert(String.format(
|
||||
"assertDirectoryContentEquals(%s): Expected %s",
|
||||
@ -808,10 +808,10 @@ public final class TKit {
|
||||
currentTest.notifyAssert();
|
||||
|
||||
var comm = Comm.compare(content, expected);
|
||||
if (!comm.unique2.isEmpty()) {
|
||||
if (!comm.unique2().isEmpty()) {
|
||||
error(String.format(
|
||||
"assertDirectoryContentContains(%s): Some expected %s. Missing %s",
|
||||
baseDir, format(comm.common), format(comm.unique2)));
|
||||
baseDir, format(comm.common()), format(comm.unique2())));
|
||||
} else {
|
||||
traceAssert(String.format(
|
||||
"assertDirectoryContentContains(%s): Expected %s",
|
||||
@ -838,21 +838,6 @@ public final class TKit {
|
||||
this.content = contents;
|
||||
}
|
||||
|
||||
private static record Comm(Set<Path> common, Set<Path> unique1, Set<Path> unique2) {
|
||||
static Comm compare(Set<Path> a, Set<Path> b) {
|
||||
Set<Path> common = new HashSet<>(a);
|
||||
common.retainAll(b);
|
||||
|
||||
Set<Path> unique1 = new HashSet<>(a);
|
||||
unique1.removeAll(common);
|
||||
|
||||
Set<Path> unique2 = new HashSet<>(b);
|
||||
unique2.removeAll(common);
|
||||
|
||||
return new Comm(common, unique1, unique2);
|
||||
}
|
||||
}
|
||||
|
||||
private static String format(Set<Path> paths) {
|
||||
return Arrays.toString(
|
||||
paths.stream().sorted().map(Path::toString).toArray(
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, 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
|
||||
@ -23,32 +23,28 @@
|
||||
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
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.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.test.Annotations.AfterEach;
|
||||
import jdk.jpackage.test.Annotations.BeforeEach;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.ParameterGroup;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Functional.ThrowingConsumer;
|
||||
import static jdk.jpackage.test.Functional.ThrowingConsumer.toConsumer;
|
||||
import jdk.jpackage.test.Functional.ThrowingFunction;
|
||||
import jdk.jpackage.test.TestMethodSupplier.InvalidAnnotationException;
|
||||
import static jdk.jpackage.test.TestMethodSupplier.MethodQuery.fromQualifiedMethodName;
|
||||
|
||||
final class TestBuilder implements AutoCloseable {
|
||||
|
||||
@ -58,6 +54,7 @@ final class TestBuilder implements AutoCloseable {
|
||||
}
|
||||
|
||||
TestBuilder(Consumer<TestInstance> testConsumer) {
|
||||
this.testMethodSupplier = TestBuilderConfig.getDefault().createTestMethodSupplier();
|
||||
argProcessors = Map.of(
|
||||
CMDLINE_ARG_PREFIX + "after-run",
|
||||
arg -> getJavaMethodsFromArg(arg).map(
|
||||
@ -70,7 +67,7 @@ final class TestBuilder implements AutoCloseable {
|
||||
CMDLINE_ARG_PREFIX + "run",
|
||||
arg -> addTestGroup(getJavaMethodsFromArg(arg).map(
|
||||
ThrowingFunction.toFunction(
|
||||
TestBuilder::toMethodCalls)).flatMap(s -> s).collect(
|
||||
this::toMethodCalls)).flatMap(s -> s).collect(
|
||||
Collectors.toList())),
|
||||
|
||||
CMDLINE_ARG_PREFIX + "exclude",
|
||||
@ -219,23 +216,29 @@ final class TestBuilder implements AutoCloseable {
|
||||
.filter(m -> m.getParameterCount() == 0)
|
||||
.filter(m -> !m.isAnnotationPresent(Test.class))
|
||||
.filter(m -> m.isAnnotationPresent(annotationType))
|
||||
.sorted((a, b) -> a.getName().compareTo(b.getName()));
|
||||
.sorted(Comparator.comparing(Method::getName));
|
||||
}
|
||||
|
||||
private static Stream<String> cmdLineArgValueToMethodNames(String v) {
|
||||
private Stream<String> cmdLineArgValueToMethodNames(String v) {
|
||||
List<String> result = new ArrayList<>();
|
||||
String defaultClassName = null;
|
||||
for (String token : v.split(",")) {
|
||||
Class testSet = probeClass(token);
|
||||
if (testSet != null) {
|
||||
if (testMethodSupplier.isTestClass(testSet)) {
|
||||
toConsumer(testMethodSupplier::verifyTestClass).accept(testSet);
|
||||
}
|
||||
|
||||
// Test set class specified. Pull in all public methods
|
||||
// from the class with @Test annotation removing name duplicates.
|
||||
// Overloads will be handled at the next phase of processing.
|
||||
defaultClassName = token;
|
||||
Stream.of(testSet.getMethods()).filter(
|
||||
m -> m.isAnnotationPresent(Test.class)).map(
|
||||
Method::getName).distinct().forEach(
|
||||
name -> result.add(String.join(".", token, name)));
|
||||
result.addAll(Stream.of(testSet.getMethods())
|
||||
.filter(m -> m.isAnnotationPresent(Test.class))
|
||||
.filter(testMethodSupplier::isEnabled)
|
||||
.map(Method::getName).distinct()
|
||||
.map(name -> String.join(".", token, name))
|
||||
.toList());
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -246,7 +249,7 @@ final class TestBuilder implements AutoCloseable {
|
||||
qualifiedMethodName = token;
|
||||
defaultClassName = token.substring(0, lastDotIdx);
|
||||
} else if (defaultClassName == null) {
|
||||
throw new ParseException("Default class name not found in");
|
||||
throw new ParseException("Missing default class name in");
|
||||
} else {
|
||||
qualifiedMethodName = String.join(".", defaultClassName, token);
|
||||
}
|
||||
@ -255,155 +258,43 @@ final class TestBuilder implements AutoCloseable {
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
private static boolean filterMethod(String expectedMethodName, Method method) {
|
||||
if (!method.getName().equals(expectedMethodName)) {
|
||||
return false;
|
||||
}
|
||||
switch (method.getParameterCount()) {
|
||||
case 0:
|
||||
return !isParametrized(method);
|
||||
case 1:
|
||||
return isParametrized(method);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isParametrized(Method method) {
|
||||
return method.isAnnotationPresent(ParameterGroup.class) || method.isAnnotationPresent(
|
||||
Parameter.class);
|
||||
}
|
||||
|
||||
private static List<Method> getJavaMethodFromString(
|
||||
String qualifiedMethodName) {
|
||||
private List<Method> getJavaMethodFromString(String qualifiedMethodName) {
|
||||
int lastDotIdx = qualifiedMethodName.lastIndexOf('.');
|
||||
if (lastDotIdx == -1) {
|
||||
throw new ParseException("Class name not found in");
|
||||
throw new ParseException("Missing class name in");
|
||||
}
|
||||
String className = qualifiedMethodName.substring(0, lastDotIdx);
|
||||
String methodName = qualifiedMethodName.substring(lastDotIdx + 1);
|
||||
Class methodClass;
|
||||
|
||||
try {
|
||||
methodClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new ParseException(String.format("Class [%s] not found;",
|
||||
className));
|
||||
return testMethodSupplier.findNullaryLikeMethods(
|
||||
fromQualifiedMethodName(qualifiedMethodName));
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new ParseException(ex.getMessage() + ";", ex);
|
||||
}
|
||||
// Get the list of all public methods as need to deal with overloads.
|
||||
List<Method> methods = Stream.of(methodClass.getMethods()).filter(
|
||||
(m) -> filterMethod(methodName, m)).collect(Collectors.toList());
|
||||
if (methods.isEmpty()) {
|
||||
throw new ParseException(String.format(
|
||||
"Method [%s] not found in [%s] class;",
|
||||
methodName, className));
|
||||
}
|
||||
|
||||
trace(String.format("%s -> %s", qualifiedMethodName, methods));
|
||||
return methods;
|
||||
}
|
||||
|
||||
private static Stream<Method> getJavaMethodsFromArg(String argValue) {
|
||||
return cmdLineArgValueToMethodNames(argValue).map(
|
||||
ThrowingFunction.toFunction(
|
||||
TestBuilder::getJavaMethodFromString)).flatMap(
|
||||
List::stream).sequential();
|
||||
private Stream<Method> getJavaMethodsFromArg(String argValue) {
|
||||
var methods = cmdLineArgValueToMethodNames(argValue)
|
||||
.map(this::getJavaMethodFromString)
|
||||
.flatMap(List::stream).toList();
|
||||
trace(String.format("%s -> %s", argValue, methods));
|
||||
return methods.stream();
|
||||
}
|
||||
|
||||
private static Parameter[] getMethodParameters(Method method) {
|
||||
if (method.isAnnotationPresent(ParameterGroup.class)) {
|
||||
return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value();
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(Parameter.class)) {
|
||||
return new Parameter[]{(Parameter) method.getAnnotation(
|
||||
Parameter.class)};
|
||||
}
|
||||
|
||||
// Unexpected
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Stream<Object[]> toCtorArgs(Method method) throws
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
Class type = method.getDeclaringClass();
|
||||
List<Method> paremetersProviders = Stream.of(type.getMethods())
|
||||
.filter(m -> m.getParameterCount() == 0)
|
||||
.filter(m -> (m.getModifiers() & Modifier.STATIC) != 0)
|
||||
.filter(m -> m.isAnnotationPresent(Parameters.class))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
if (paremetersProviders.isEmpty()) {
|
||||
// Single instance using the default constructor.
|
||||
return Stream.ofNullable(MethodCall.DEFAULT_CTOR_ARGS);
|
||||
}
|
||||
|
||||
// Pick the first method from the list.
|
||||
Method paremetersProvider = paremetersProviders.iterator().next();
|
||||
if (paremetersProviders.size() > 1) {
|
||||
trace(String.format(
|
||||
"Found %d public static methods without arguments with %s annotation. Will use %s",
|
||||
paremetersProviders.size(), Parameters.class,
|
||||
paremetersProvider));
|
||||
paremetersProviders.stream().map(Method::toString).forEach(
|
||||
TestBuilder::trace);
|
||||
}
|
||||
|
||||
// Construct collection of arguments for test class instances.
|
||||
return ((Collection) paremetersProvider.invoke(null)).stream();
|
||||
}
|
||||
|
||||
private static Stream<MethodCall> toMethodCalls(Method method) throws
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(
|
||||
s -> s).peek(methodCall -> {
|
||||
// Make sure required constructor is accessible if the one is needed.
|
||||
// Need to probe all methods as some of them might be static
|
||||
// and some class members.
|
||||
// Only class members require ctors.
|
||||
try {
|
||||
methodCall.checkRequiredConstructor();
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new ParseException(ex.getMessage() + ".");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<MethodCall> toMethodCalls(Object[] ctorArgs, Method method) {
|
||||
if (!isParametrized(method)) {
|
||||
return Stream.of(new MethodCall(ctorArgs, method));
|
||||
}
|
||||
Parameter[] annotations = getMethodParameters(method);
|
||||
if (annotations.length == 0) {
|
||||
return Stream.of(new MethodCall(ctorArgs, method));
|
||||
}
|
||||
return Stream.of(annotations).map((a) -> {
|
||||
Class paramType = method.getParameterTypes()[0];
|
||||
final Object annotationValue;
|
||||
if (!paramType.isArray()) {
|
||||
annotationValue = fromString(a.value()[0], paramType);
|
||||
} else {
|
||||
Class paramComponentType = paramType.getComponentType();
|
||||
annotationValue = Array.newInstance(paramComponentType, a.value().length);
|
||||
var idx = new AtomicInteger(-1);
|
||||
Stream.of(a.value()).map(v -> fromString(v, paramComponentType)).sequential().forEach(
|
||||
v -> Array.set(annotationValue, idx.incrementAndGet(), v));
|
||||
private Stream<MethodCall> toMethodCalls(Method method) throws
|
||||
IllegalAccessException, InvocationTargetException, InvalidAnnotationException {
|
||||
return testMethodSupplier.mapToMethodCalls(method).peek(methodCall -> {
|
||||
// Make sure required constructor is accessible if the one is needed.
|
||||
// Need to probe all methods as some of them might be static
|
||||
// and some class members.
|
||||
// Only class members require ctors.
|
||||
try {
|
||||
methodCall.checkRequiredConstructor();
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new ParseException(ex.getMessage() + ".", ex);
|
||||
}
|
||||
return new MethodCall(ctorArgs, method, annotationValue);
|
||||
});
|
||||
}
|
||||
|
||||
private static Object fromString(String value, Class toType) {
|
||||
if (toType.isEnum()) {
|
||||
return Enum.valueOf(toType, value);
|
||||
}
|
||||
Function<String, Object> converter = conv.get(toType);
|
||||
if (converter == null) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Failed to find a conversion of [%s] string to %s type",
|
||||
value, toType));
|
||||
}
|
||||
return converter.apply(value);
|
||||
}
|
||||
|
||||
// Wraps Method.invike() into ThrowingRunnable.run()
|
||||
private ThrowingConsumer wrap(Method method) {
|
||||
return (test) -> {
|
||||
@ -427,6 +318,10 @@ final class TestBuilder implements AutoCloseable {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
ParseException(String msg, Exception ex) {
|
||||
super(msg, ex);
|
||||
}
|
||||
|
||||
void setContext(String badCmdLineArg) {
|
||||
this.badCmdLineArg = badCmdLineArg;
|
||||
}
|
||||
@ -448,8 +343,9 @@ final class TestBuilder implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private final TestMethodSupplier testMethodSupplier;
|
||||
private final Map<String, ThrowingConsumer<String>> argProcessors;
|
||||
private Consumer<TestInstance> testConsumer;
|
||||
private final Consumer<TestInstance> testConsumer;
|
||||
private List<MethodCall> testGroup;
|
||||
private List<ThrowingConsumer> beforeActions;
|
||||
private List<ThrowingConsumer> afterActions;
|
||||
@ -458,14 +354,5 @@ final class TestBuilder implements AutoCloseable {
|
||||
private String spaceSubstitute;
|
||||
private boolean dryRun;
|
||||
|
||||
private final static Map<Class, Function<String, Object>> conv = Map.of(
|
||||
boolean.class, Boolean::valueOf,
|
||||
Boolean.class, Boolean::valueOf,
|
||||
int.class, Integer::valueOf,
|
||||
Integer.class, Integer::valueOf,
|
||||
long.class, Long::valueOf,
|
||||
Long.class, Long::valueOf,
|
||||
String.class, String::valueOf);
|
||||
|
||||
final static String CMDLINE_ARG_PREFIX = "--jpt-";
|
||||
static final String CMDLINE_ARG_PREFIX = "--jpt-";
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.util.Objects;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
|
||||
final class TestBuilderConfig {
|
||||
TestBuilderConfig() {
|
||||
}
|
||||
|
||||
TestMethodSupplier createTestMethodSupplier() {
|
||||
return new TestMethodSupplier(os);
|
||||
}
|
||||
|
||||
OperatingSystem getOperatingSystem() {
|
||||
return os;
|
||||
}
|
||||
|
||||
static TestBuilderConfig getDefault() {
|
||||
return DEFAULT.get();
|
||||
}
|
||||
|
||||
static void setOperatingSystem(OperatingSystem os) {
|
||||
Objects.requireNonNull(os);
|
||||
DEFAULT.get().os = os;
|
||||
}
|
||||
|
||||
static void setDefaults() {
|
||||
DEFAULT.set(new TestBuilderConfig());
|
||||
}
|
||||
|
||||
private OperatingSystem os = OperatingSystem.current();
|
||||
|
||||
private static final ThreadLocal<TestBuilderConfig> DEFAULT = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected TestBuilderConfig initialValue() {
|
||||
return new TestBuilderConfig();
|
||||
}
|
||||
};
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020, 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
|
||||
@ -32,8 +32,10 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@ -50,7 +52,7 @@ final class TestInstance implements ThrowingRunnable {
|
||||
|
||||
String testFullName() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(clazz.getSimpleName());
|
||||
sb.append(clazz.getName());
|
||||
if (instanceArgs != null) {
|
||||
sb.append('(').append(instanceArgs).append(')');
|
||||
}
|
||||
@ -78,12 +80,12 @@ final class TestInstance implements ThrowingRunnable {
|
||||
}
|
||||
|
||||
Builder ctorArgs(Object... v) {
|
||||
ctorArgs = ofNullable(v);
|
||||
ctorArgs = Arrays.asList(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder methodArgs(Object... v) {
|
||||
methodArgs = ofNullable(v);
|
||||
methodArgs = Arrays.asList(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -107,22 +109,18 @@ final class TestInstance implements ThrowingRunnable {
|
||||
}
|
||||
return values.stream().map(v -> {
|
||||
if (v != null && v.getClass().isArray()) {
|
||||
return String.format("%s(length=%d)",
|
||||
Arrays.deepToString((Object[]) v),
|
||||
Array.getLength(v));
|
||||
String asString;
|
||||
if (v.getClass().getComponentType().isPrimitive()) {
|
||||
asString = PRIMITIVE_ARRAY_FORMATTERS.get(v.getClass()).apply(v);
|
||||
} else {
|
||||
asString = Arrays.deepToString((Object[]) v);
|
||||
}
|
||||
return String.format("%s(length=%d)", asString, Array.getLength(v));
|
||||
}
|
||||
return String.format("%s", v);
|
||||
}).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
private static List<Object> ofNullable(Object... values) {
|
||||
List<Object> result = new ArrayList();
|
||||
for (var v: values) {
|
||||
result.add(v);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Object> ctorArgs;
|
||||
private List<Object> methodArgs;
|
||||
private Method method;
|
||||
@ -331,7 +329,7 @@ final class TestInstance implements ThrowingRunnable {
|
||||
private final boolean dryRun;
|
||||
private final Path workDir;
|
||||
|
||||
private final static Set<Status> KEEP_WORK_DIR = Functional.identity(
|
||||
private static final Set<Status> KEEP_WORK_DIR = Functional.identity(
|
||||
() -> {
|
||||
final String propertyName = "keep-work-dir";
|
||||
Set<String> keepWorkDir = TKit.tokenizeConfigProperty(
|
||||
@ -355,4 +353,15 @@ final class TestInstance implements ThrowingRunnable {
|
||||
return Collections.unmodifiableSet(result);
|
||||
}).get();
|
||||
|
||||
private static final Map<Class<?>, Function<Object, String>> PRIMITIVE_ARRAY_FORMATTERS = Map.of(
|
||||
boolean[].class, v -> Arrays.toString((boolean[])v),
|
||||
byte[].class, v -> Arrays.toString((byte[])v),
|
||||
char[].class, v -> Arrays.toString((char[])v),
|
||||
short[].class, v -> Arrays.toString((short[])v),
|
||||
int[].class, v -> Arrays.toString((int[])v),
|
||||
long[].class, v -> Arrays.toString((long[])v),
|
||||
float[].class, v -> Arrays.toString((float[])v),
|
||||
double[].class, v -> Arrays.toString((double[])v)
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.ParameterGroup;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplierGroup;
|
||||
import jdk.jpackage.test.Annotations.Parameters;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import static jdk.jpackage.test.Functional.ThrowingFunction.toFunction;
|
||||
import static jdk.jpackage.test.Functional.ThrowingSupplier.toSupplier;
|
||||
import static jdk.jpackage.test.MethodCall.mapArgs;
|
||||
|
||||
final class TestMethodSupplier {
|
||||
|
||||
TestMethodSupplier(OperatingSystem os) {
|
||||
Objects.requireNonNull(os);
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
record MethodQuery(String className, String methodName) {
|
||||
|
||||
List<Method> lookup() throws ClassNotFoundException {
|
||||
final Class methodClass = Class.forName(className);
|
||||
|
||||
// Get the list of all public methods as need to deal with overloads.
|
||||
return Stream.of(methodClass.getMethods()).filter(method -> {
|
||||
return method.getName().equals(methodName);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
static MethodQuery fromQualifiedMethodName(String qualifiedMethodName) {
|
||||
int lastDotIdx = qualifiedMethodName.lastIndexOf('.');
|
||||
if (lastDotIdx == -1) {
|
||||
throw new IllegalArgumentException("Class name not specified");
|
||||
}
|
||||
|
||||
var className = qualifiedMethodName.substring(0, lastDotIdx);
|
||||
var methodName = qualifiedMethodName.substring(lastDotIdx + 1);
|
||||
|
||||
return new MethodQuery(className, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
List<Method> findNullaryLikeMethods(MethodQuery query) throws NoSuchMethodException {
|
||||
List<Method> methods;
|
||||
|
||||
try {
|
||||
methods = query.lookup();
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new NoSuchMethodException(
|
||||
String.format("Class [%s] not found", query.className()));
|
||||
}
|
||||
|
||||
if (methods.isEmpty()) {
|
||||
throw new NoSuchMethodException(String.format(
|
||||
"Public method [%s] not found in [%s] class",
|
||||
query.methodName(), query.className()));
|
||||
}
|
||||
|
||||
methods = methods.stream().filter(method -> {
|
||||
if (isParameterized(method) && isTest(method)) {
|
||||
// Always accept test method with annotations producing arguments for its invocation.
|
||||
return true;
|
||||
} else {
|
||||
return method.getParameterCount() == 0;
|
||||
}
|
||||
}).filter(this::isEnabled).toList();
|
||||
|
||||
if (methods.isEmpty()) {
|
||||
throw new NoSuchMethodException(String.format(
|
||||
"Suitable public method [%s] not found in [%s] class",
|
||||
query.methodName(), query.className()));
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
boolean isTestClass(Class<?> type) {
|
||||
var typeStatus = processedTypes.get(type);
|
||||
if (typeStatus == null) {
|
||||
typeStatus = Verifier.isTestClass(type) ? TypeStatus.TEST_CLASS : TypeStatus.NOT_TEST_CLASS;
|
||||
processedTypes.put(type, typeStatus);
|
||||
}
|
||||
|
||||
return !TypeStatus.NOT_TEST_CLASS.equals(typeStatus);
|
||||
}
|
||||
|
||||
void verifyTestClass(Class<?> type) throws InvalidAnnotationException {
|
||||
var typeStatus = processedTypes.get(type);
|
||||
if (typeStatus == null) {
|
||||
// The "type" has not been verified yet.
|
||||
try {
|
||||
Verifier.verifyTestClass(type);
|
||||
processedTypes.put(type, TypeStatus.VALID_TEST_CLASS);
|
||||
return;
|
||||
} catch (InvalidAnnotationException ex) {
|
||||
processedTypes.put(type, TypeStatus.TEST_CLASS);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
switch (typeStatus) {
|
||||
case NOT_TEST_CLASS -> Verifier.throwNotTestClassException(type);
|
||||
case TEST_CLASS -> Verifier.verifyTestClass(type);
|
||||
case VALID_TEST_CLASS -> {}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isEnabled(Method method) {
|
||||
return Stream.of(Test.class, Parameters.class)
|
||||
.filter(method::isAnnotationPresent)
|
||||
.findFirst()
|
||||
.map(method::getAnnotation)
|
||||
.map(this::canRunOnTheOperatingSystem)
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
Stream<MethodCall> mapToMethodCalls(Method method) throws
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(x -> x);
|
||||
}
|
||||
|
||||
private Stream<Object[]> toCtorArgs(Method method) throws
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
|
||||
if ((method.getModifiers() & Modifier.STATIC) != 0) {
|
||||
// Static method, no instance
|
||||
return Stream.ofNullable(DEFAULT_CTOR_ARGS);
|
||||
}
|
||||
|
||||
final var type = method.getDeclaringClass();
|
||||
|
||||
final var paremeterSuppliers = filterParameterSuppliers(type)
|
||||
.filter(m -> m.isAnnotationPresent(Parameters.class))
|
||||
.filter(this::isEnabled)
|
||||
.sorted(Comparator.comparing(Method::getName)).toList();
|
||||
if (paremeterSuppliers.isEmpty()) {
|
||||
// Single instance using the default constructor.
|
||||
return Stream.ofNullable(DEFAULT_CTOR_ARGS);
|
||||
}
|
||||
|
||||
// Construct collection of arguments for test class instances.
|
||||
return createArgs(paremeterSuppliers.toArray(Method[]::new));
|
||||
}
|
||||
|
||||
private Stream<MethodCall> toMethodCalls(Object[] ctorArgs, Method method) {
|
||||
if (!isParameterized(method)) {
|
||||
return Stream.of(new MethodCall(ctorArgs, method));
|
||||
}
|
||||
|
||||
var fromParameter = Stream.of(getMethodParameters(method)).map(a -> {
|
||||
return createArgsForAnnotation(method, a);
|
||||
}).flatMap(List::stream);
|
||||
|
||||
var fromParameterSupplier = Stream.of(getMethodParameterSuppliers(method)).map(a -> {
|
||||
return toSupplier(() -> createArgsForAnnotation(method, a)).get();
|
||||
}).flatMap(List::stream);
|
||||
|
||||
return Stream.concat(fromParameter, fromParameterSupplier).map(args -> {
|
||||
return new MethodCall(ctorArgs, method, args);
|
||||
});
|
||||
}
|
||||
|
||||
private List<Object[]> createArgsForAnnotation(Executable exec, Parameter a) {
|
||||
if (!canRunOnTheOperatingSystem(a)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
final var annotationArgs = a.value();
|
||||
final var execParameterTypes = exec.getParameterTypes();
|
||||
|
||||
if (execParameterTypes.length > annotationArgs.length) {
|
||||
if (execParameterTypes.length - annotationArgs.length == 1 && exec.isVarArgs()) {
|
||||
} else {
|
||||
throw new RuntimeException(String.format(
|
||||
"Not enough annotation values %s for [%s]",
|
||||
List.of(annotationArgs), exec));
|
||||
}
|
||||
}
|
||||
|
||||
final Class<?>[] argTypes;
|
||||
if (exec.isVarArgs()) {
|
||||
List<Class<?>> argTypesBuilder = new ArrayList<>();
|
||||
var lastExecParameterTypeIdx = execParameterTypes.length - 1;
|
||||
argTypesBuilder.addAll(List.of(execParameterTypes).subList(0,
|
||||
lastExecParameterTypeIdx));
|
||||
argTypesBuilder.addAll(Collections.nCopies(
|
||||
Integer.max(0, annotationArgs.length - lastExecParameterTypeIdx),
|
||||
execParameterTypes[lastExecParameterTypeIdx].componentType()));
|
||||
argTypes = argTypesBuilder.toArray(Class[]::new);
|
||||
} else {
|
||||
argTypes = execParameterTypes;
|
||||
}
|
||||
|
||||
if (argTypes.length < annotationArgs.length) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Too many annotation values %s for [%s]",
|
||||
List.of(annotationArgs), exec));
|
||||
}
|
||||
|
||||
var args = mapArgs(exec, IntStream.range(0, argTypes.length).mapToObj(idx -> {
|
||||
return fromString(annotationArgs[idx], argTypes[idx]);
|
||||
}).toArray(Object[]::new));
|
||||
|
||||
return List.<Object[]>of(args);
|
||||
}
|
||||
|
||||
private List<Object[]> createArgsForAnnotation(Executable exec,
|
||||
ParameterSupplier a) throws IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
if (!canRunOnTheOperatingSystem(a)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
final Class<?> execClass = exec.getDeclaringClass();
|
||||
final var supplierFuncName = a.value();
|
||||
|
||||
final MethodQuery methodQuery;
|
||||
if (!a.value().contains(".")) {
|
||||
// No class name specified
|
||||
methodQuery = new MethodQuery(execClass.getName(), a.value());
|
||||
} else {
|
||||
methodQuery = MethodQuery.fromQualifiedMethodName(supplierFuncName);
|
||||
}
|
||||
|
||||
final Method supplierMethod;
|
||||
try {
|
||||
final var parameterSupplierCandidates = findNullaryLikeMethods(methodQuery);
|
||||
final Function<String, Class> classForName = toFunction(Class::forName);
|
||||
final var supplierMethodClass = classForName.apply(methodQuery.className());
|
||||
if (parameterSupplierCandidates.isEmpty()) {
|
||||
throw new RuntimeException(String.format(
|
||||
"No parameter suppliers in [%s] class",
|
||||
supplierMethodClass.getName()));
|
||||
}
|
||||
|
||||
var allParameterSuppliers = filterParameterSuppliers(supplierMethodClass).toList();
|
||||
|
||||
supplierMethod = findNullaryLikeMethods(methodQuery)
|
||||
.stream()
|
||||
.filter(allParameterSuppliers::contains)
|
||||
.findFirst().orElseThrow(() -> {
|
||||
var msg = String.format(
|
||||
"No suitable parameter supplier found for %s(%s) annotation",
|
||||
a, supplierFuncName);
|
||||
trace(String.format(
|
||||
"%s. Parameter suppliers of %s class:", msg,
|
||||
execClass.getName()));
|
||||
IntStream.range(0, allParameterSuppliers.size()).mapToObj(idx -> {
|
||||
return String.format(" [%d/%d] %s()", idx + 1,
|
||||
allParameterSuppliers.size(),
|
||||
allParameterSuppliers.get(idx).getName());
|
||||
}).forEachOrdered(TestMethodSupplier::trace);
|
||||
|
||||
return new RuntimeException(msg);
|
||||
});
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Method not found for %s(%s) annotation", a, supplierFuncName));
|
||||
}
|
||||
|
||||
return createArgs(supplierMethod).map(args -> {
|
||||
return mapArgs(exec, args);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private boolean canRunOnTheOperatingSystem(Annotation a) {
|
||||
switch (a) {
|
||||
case Test t -> {
|
||||
return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS());
|
||||
}
|
||||
case Parameters t -> {
|
||||
return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS());
|
||||
}
|
||||
case Parameter t -> {
|
||||
return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS());
|
||||
}
|
||||
case ParameterSupplier t -> {
|
||||
return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS());
|
||||
}
|
||||
default -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isParameterized(Method method) {
|
||||
return Stream.of(
|
||||
Parameter.class, ParameterGroup.class,
|
||||
ParameterSupplier.class, ParameterSupplierGroup.class
|
||||
).anyMatch(method::isAnnotationPresent);
|
||||
}
|
||||
|
||||
private static boolean isTest(Method method) {
|
||||
return method.isAnnotationPresent(Test.class);
|
||||
}
|
||||
|
||||
private static boolean canRunOnTheOperatingSystem(OperatingSystem value,
|
||||
OperatingSystem[] include, OperatingSystem[] exclude) {
|
||||
Set<OperatingSystem> suppordOperatingSystems = new HashSet<>();
|
||||
suppordOperatingSystems.addAll(List.of(include));
|
||||
suppordOperatingSystems.removeAll(List.of(exclude));
|
||||
return suppordOperatingSystems.contains(value);
|
||||
}
|
||||
|
||||
private static Parameter[] getMethodParameters(Method method) {
|
||||
if (method.isAnnotationPresent(ParameterGroup.class)) {
|
||||
return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value();
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(Parameter.class)) {
|
||||
return new Parameter[]{(Parameter) method.getAnnotation(Parameter.class)};
|
||||
}
|
||||
|
||||
return new Parameter[0];
|
||||
}
|
||||
|
||||
private static ParameterSupplier[] getMethodParameterSuppliers(Method method) {
|
||||
if (method.isAnnotationPresent(ParameterSupplierGroup.class)) {
|
||||
return ((ParameterSupplierGroup) method.getAnnotation(ParameterSupplierGroup.class)).value();
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(ParameterSupplier.class)) {
|
||||
return new ParameterSupplier[]{(ParameterSupplier) method.getAnnotation(
|
||||
ParameterSupplier.class)};
|
||||
}
|
||||
|
||||
return new ParameterSupplier[0];
|
||||
}
|
||||
|
||||
private static Stream<Method> filterParameterSuppliers(Class<?> type) {
|
||||
return Stream.of(type.getMethods())
|
||||
.filter(m -> m.getParameterCount() == 0)
|
||||
.filter(m -> (m.getModifiers() & Modifier.STATIC) != 0)
|
||||
.sorted(Comparator.comparing(Method::getName));
|
||||
}
|
||||
|
||||
private static Stream<Object[]> createArgs(Method ... parameterSuppliers) throws
|
||||
IllegalAccessException, InvocationTargetException {
|
||||
List<Object[]> args = new ArrayList<>();
|
||||
for (var parameterSupplier : parameterSuppliers) {
|
||||
args.addAll((Collection) parameterSupplier.invoke(null));
|
||||
}
|
||||
return args.stream();
|
||||
}
|
||||
|
||||
private static Object fromString(String value, Class toType) {
|
||||
if (toType.isEnum()) {
|
||||
return Enum.valueOf(toType, value);
|
||||
}
|
||||
Function<String, Object> converter = FROM_STRING.get(toType);
|
||||
if (converter == null) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Failed to find a conversion of [%s] string to %s type",
|
||||
value, toType.getName()));
|
||||
}
|
||||
return converter.apply(value);
|
||||
}
|
||||
|
||||
private static void trace(String msg) {
|
||||
if (TKit.VERBOSE_TEST_SETUP) {
|
||||
TKit.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static class InvalidAnnotationException extends Exception {
|
||||
InvalidAnnotationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Verifier {
|
||||
static boolean isTestClass(Class<?> type) {
|
||||
for (var method : type.getDeclaredMethods()) {
|
||||
if (isParameterized(method) || isTest(method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void verifyTestClass(Class<?> type) throws InvalidAnnotationException {
|
||||
boolean withTestAnnotations = false;
|
||||
for (var method : type.getDeclaredMethods()) {
|
||||
if (!withTestAnnotations && (isParameterized(method) || isTest(method))) {
|
||||
withTestAnnotations = true;
|
||||
}
|
||||
verifyAnnotationsCorrect(method);
|
||||
}
|
||||
|
||||
if (!withTestAnnotations) {
|
||||
throwNotTestClassException(type);
|
||||
}
|
||||
}
|
||||
|
||||
static void throwNotTestClassException(Class<?> type) throws InvalidAnnotationException {
|
||||
throw new InvalidAnnotationException(String.format(
|
||||
"Type [%s] is not a test class", type.getName()));
|
||||
}
|
||||
|
||||
private static void verifyAnnotationsCorrect(Method method) throws
|
||||
InvalidAnnotationException {
|
||||
var parameterized = isParameterized(method);
|
||||
if (parameterized && !isTest(method)) {
|
||||
throw new InvalidAnnotationException(String.format(
|
||||
"Missing %s annotation on [%s] method", Test.class.getName(), method));
|
||||
}
|
||||
|
||||
var isPublic = Modifier.isPublic(method.getModifiers());
|
||||
|
||||
if (isTest(method) && !isPublic) {
|
||||
throw new InvalidAnnotationException(String.format(
|
||||
"Non-public method [%s] with %s annotation",
|
||||
method, Test.class.getName()));
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(Parameters.class) && !isPublic) {
|
||||
throw new InvalidAnnotationException(String.format(
|
||||
"Non-public method [%s] with %s annotation",
|
||||
method, Test.class.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum TypeStatus {
|
||||
NOT_TEST_CLASS,
|
||||
TEST_CLASS,
|
||||
VALID_TEST_CLASS,
|
||||
}
|
||||
|
||||
private final OperatingSystem os;
|
||||
private final Map<Class<?>, TypeStatus> processedTypes = new HashMap<>();
|
||||
|
||||
private static final Object[] DEFAULT_CTOR_ARGS = new Object[0];
|
||||
|
||||
private static final Map<Class, Function<String, Object>> FROM_STRING;
|
||||
|
||||
static {
|
||||
Map<Class, Function<String, Object>> primitives = Map.of(
|
||||
boolean.class, Boolean::valueOf,
|
||||
byte.class, Byte::valueOf,
|
||||
short.class, Short::valueOf,
|
||||
int.class, Integer::valueOf,
|
||||
long.class, Long::valueOf,
|
||||
float.class, Float::valueOf,
|
||||
double.class, Double::valueOf);
|
||||
|
||||
Map<Class, Function<String, Object>> boxed = Map.of(
|
||||
Boolean.class, Boolean::valueOf,
|
||||
Byte.class, Byte::valueOf,
|
||||
Short.class, Short::valueOf,
|
||||
Integer.class, Integer::valueOf,
|
||||
Long.class, Long::valueOf,
|
||||
Float.class, Float::valueOf,
|
||||
Double.class, Double::valueOf);
|
||||
|
||||
Map<Class, Function<String, Object>> other = Map.of(
|
||||
String.class, String::valueOf,
|
||||
Path.class, Path::of);
|
||||
|
||||
Map<Class, Function<String, Object>> combined = new HashMap<>(primitives);
|
||||
combined.putAll(other);
|
||||
combined.putAll(boxed);
|
||||
|
||||
FROM_STRING = Collections.unmodifiableMap(combined);
|
||||
}
|
||||
}
|
@ -29,12 +29,12 @@ 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.internal.util.OperatingSystem;
|
||||
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.Parameters;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Functional.ThrowingConsumer;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
@ -55,47 +55,51 @@ import jdk.jpackage.test.TKit;
|
||||
*/
|
||||
public final class InOutPathTest {
|
||||
|
||||
@Annotations.Parameters
|
||||
@Parameters
|
||||
public static Collection input() {
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
|
||||
for (var packageTypes : List.of(PackageType.IMAGE.toString(), ALL_NATIVE_PACKAGE_TYPES)) {
|
||||
for (var packageTypeAlias : PackageTypeAlias.values()) {
|
||||
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 -> {
|
||||
{packageTypeAlias, wrap(InOutPathTest::outputDirInInputDir, "--dest in --input")},
|
||||
{packageTypeAlias, wrap(InOutPathTest::outputDirSameAsInputDir, "--dest same as --input")},
|
||||
{packageTypeAlias, wrap(InOutPathTest::tempDirInInputDir, "--temp in --input")},
|
||||
{packageTypeAlias, wrap(cmd -> {
|
||||
outputDirInInputDir(cmd);
|
||||
tempDirInInputDir(cmd);
|
||||
}, "--dest and --temp in --input")},
|
||||
}));
|
||||
data.addAll(additionalContentInput(packageTypes, "--app-content"));
|
||||
}
|
||||
|
||||
if (!TKit.isOSX()) {
|
||||
data.addAll(List.of(new Object[][]{
|
||||
{PackageType.IMAGE.toString(), wrap(cmd -> {
|
||||
additionalContent(cmd, "--app-content", cmd.outputBundle());
|
||||
}, "--app-content same as output bundle")},
|
||||
}));
|
||||
} else {
|
||||
var contentsFolder = "Contents/MacOS";
|
||||
data.addAll(List.of(new Object[][]{
|
||||
{PackageType.IMAGE.toString(), wrap(cmd -> {
|
||||
additionalContent(cmd, "--app-content", cmd.outputBundle().resolve(contentsFolder));
|
||||
}, String.format("--app-content same as the \"%s\" folder in the output bundle", contentsFolder))},
|
||||
}));
|
||||
}
|
||||
|
||||
if (TKit.isOSX()) {
|
||||
data.addAll(additionalContentInput(PackageType.MAC_DMG.toString(),
|
||||
"--mac-dmg-content"));
|
||||
data.addAll(additionalContentInput(packageTypeAlias, "--app-content"));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static List<Object[]> additionalContentInput(String packageTypes, String argName) {
|
||||
@Parameters(ifNotOS = OperatingSystem.MACOS)
|
||||
public static Collection<Object[]> appContentInputOther() {
|
||||
return List.of(new Object[][]{
|
||||
{PackageTypeAlias.IMAGE, wrap(cmd -> {
|
||||
additionalContent(cmd, "--app-content", cmd.outputBundle());
|
||||
}, "--app-content same as output bundle")},
|
||||
});
|
||||
}
|
||||
|
||||
@Parameters(ifOS = OperatingSystem.MACOS)
|
||||
public static Collection<Object[]> appContentInputOSX() {
|
||||
var contentsFolder = "Contents/MacOS";
|
||||
return List.of(new Object[][]{
|
||||
{PackageTypeAlias.IMAGE, wrap(cmd -> {
|
||||
additionalContent(cmd, "--app-content", cmd.outputBundle().resolve(contentsFolder));
|
||||
}, String.format("--app-content same as the \"%s\" folder in the output bundle", contentsFolder))},
|
||||
});
|
||||
}
|
||||
|
||||
@Parameters(ifOS = OperatingSystem.MACOS)
|
||||
public static Collection<Object[]> inputOSX() {
|
||||
return List.of(additionalContentInput(PackageType.MAC_DMG, "--mac-dmg-content").toArray(Object[][]::new));
|
||||
}
|
||||
|
||||
private static List<Object[]> additionalContentInput(Object packageTypes, String argName) {
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
|
||||
data.addAll(List.of(new Object[][]{
|
||||
@ -127,13 +131,16 @@ public final class InOutPathTest {
|
||||
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());
|
||||
}
|
||||
public InOutPathTest(PackageTypeAlias packageTypeAlias, Envelope configure) {
|
||||
this(packageTypeAlias.packageTypes, configure);
|
||||
}
|
||||
|
||||
public InOutPathTest(PackageType packageType, Envelope configure) {
|
||||
this(Set.of(packageType), configure);
|
||||
}
|
||||
|
||||
public InOutPathTest(Set<PackageType> packageTypes, Envelope configure) {
|
||||
this.packageTypes = packageTypes;
|
||||
this.configure = configure.value;
|
||||
}
|
||||
|
||||
@ -271,6 +278,18 @@ public final class InOutPathTest {
|
||||
}
|
||||
}
|
||||
|
||||
private enum PackageTypeAlias {
|
||||
IMAGE(Set.of(PackageType.IMAGE)),
|
||||
NATIVE(PackageType.NATIVE),
|
||||
;
|
||||
|
||||
PackageTypeAlias(Set<PackageType> packageTypes) {
|
||||
this.packageTypes = packageTypes;
|
||||
}
|
||||
|
||||
private final Set<PackageType> packageTypes;
|
||||
}
|
||||
|
||||
private final Set<PackageType> packageTypes;
|
||||
private final ThrowingConsumer<JPackageCommand> configure;
|
||||
|
||||
@ -279,6 +298,4 @@ public final class InOutPathTest {
|
||||
// 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";
|
||||
}
|
||||
|
@ -22,13 +22,12 @@
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jdk.internal.util.OperatingSystem;
|
||||
import jdk.jpackage.test.TKit;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.Functional;
|
||||
import jdk.jpackage.test.Annotations.Parameter;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
|
||||
/**
|
||||
* Test --install-dir parameter. Output of the test should be
|
||||
@ -76,28 +75,18 @@ import jdk.jpackage.test.Annotations.Parameter;
|
||||
*/
|
||||
public class InstallDirTest {
|
||||
|
||||
public static void testCommon() {
|
||||
final Map<PackageType, Path> INSTALL_DIRS = Functional.identity(() -> {
|
||||
Map<PackageType, Path> reply = new HashMap<>();
|
||||
reply.put(PackageType.WIN_MSI, Path.of("TestVendor\\InstallDirTest1234"));
|
||||
reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI));
|
||||
|
||||
reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage"));
|
||||
reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB));
|
||||
|
||||
reply.put(PackageType.MAC_PKG, Path.of("/Applications/jpackage"));
|
||||
reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG));
|
||||
|
||||
return reply;
|
||||
}).get();
|
||||
|
||||
@Test
|
||||
@Parameter(value = "TestVendor\\InstallDirTest1234", ifOS = OperatingSystem.WINDOWS)
|
||||
@Parameter(value = "/opt/jpackage", ifOS = OperatingSystem.LINUX)
|
||||
@Parameter(value = "/Applications/jpackage", ifOS = OperatingSystem.MACOS)
|
||||
public static void testCommon(Path installDir) {
|
||||
new PackageTest().configureHelloApp()
|
||||
.addInitializer(cmd -> {
|
||||
cmd.addArguments("--install-dir", INSTALL_DIRS.get(
|
||||
cmd.packageType()));
|
||||
cmd.addArguments("--install-dir", installDir);
|
||||
}).run();
|
||||
}
|
||||
|
||||
@Test(ifOS = OperatingSystem.LINUX)
|
||||
@Parameter("/")
|
||||
@Parameter(".")
|
||||
@Parameter("foo")
|
||||
|
Loading…
Reference in New Issue
Block a user