test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java
branchJDK-8200758-branch
changeset 58648 3bf53ffa9ae7
parent 58416 f09bf58c1f17
equal deleted inserted replaced
58647:2c43b89b1679 58648:3bf53ffa9ae7
    21  * questions.
    21  * questions.
    22  */
    22  */
    23 
    23 
    24 package jdk.jpackage.test;
    24 package jdk.jpackage.test;
    25 
    25 
    26 import java.lang.reflect.Constructor;
    26 import java.lang.reflect.Array;
    27 import java.lang.reflect.InvocationTargetException;
    27 import java.lang.reflect.InvocationTargetException;
    28 import java.lang.reflect.Method;
    28 import java.lang.reflect.Method;
    29 import java.util.ArrayList;
    29 import java.lang.reflect.Modifier;
    30 import java.util.List;
    30 import java.util.*;
    31 import java.util.Map;
    31 import java.util.concurrent.atomic.AtomicInteger;
    32 import java.util.function.Consumer;
    32 import java.util.function.Consumer;
    33 import java.util.function.Function;
    33 import java.util.function.Function;
       
    34 import java.util.function.UnaryOperator;
    34 import java.util.stream.Collectors;
    35 import java.util.stream.Collectors;
    35 import java.util.stream.Stream;
    36 import java.util.stream.Stream;
       
    37 import jdk.jpackage.test.Annotations.AfterEach;
       
    38 import jdk.jpackage.test.Annotations.BeforeEach;
    36 import jdk.jpackage.test.Annotations.Parameter;
    39 import jdk.jpackage.test.Annotations.Parameter;
       
    40 import jdk.jpackage.test.Annotations.ParameterGroup;
    37 import jdk.jpackage.test.Annotations.Parameters;
    41 import jdk.jpackage.test.Annotations.Parameters;
    38 import jdk.jpackage.test.Annotations.Test;
    42 import jdk.jpackage.test.Annotations.Test;
    39 import jdk.jpackage.test.Functional.ThrowingConsumer;
    43 import jdk.jpackage.test.Functional.ThrowingConsumer;
    40 import jdk.jpackage.test.Functional.ThrowingFunction;
    44 import jdk.jpackage.test.Functional.ThrowingFunction;
    41 import jdk.jpackage.test.Functional.ThrowingSupplier;
       
    42 
    45 
    43 final class TestBuilder implements AutoCloseable {
    46 final class TestBuilder implements AutoCloseable {
    44 
    47 
    45     @Override
    48     @Override
    46     public void close() throws Exception {
    49     public void close() throws Exception {
    47         flushTestGroup(null);
    50         flushTestGroup();
    48     }
    51     }
    49 
    52 
    50     TestBuilder(Consumer<TestInstance> testConsumer) {
    53     TestBuilder(Consumer<TestInstance> testConsumer) {
    51         argProcessors = Map.of(CMDLINE_ARG_PREFIX + "after-run",
    54         argProcessors = Map.of(
    52                 arg -> getJavaMethodsFromArg(arg).forEach(
    55                 CMDLINE_ARG_PREFIX + "after-run",
    53                         (method) -> afterActions.add(wrap(method, dryRun))),
    56                 arg -> getJavaMethodsFromArg(arg).map(
       
    57                         this::wrap).forEachOrdered(afterActions::add),
       
    58 
    54                 CMDLINE_ARG_PREFIX + "before-run",
    59                 CMDLINE_ARG_PREFIX + "before-run",
    55                 arg -> getJavaMethodsFromArg(arg).forEach(
    60                 arg -> getJavaMethodsFromArg(arg).map(
    56                         (method) -> beforeActions.add(wrap(method, dryRun))),
    61                         this::wrap).forEachOrdered(beforeActions::add),
       
    62 
    57                 CMDLINE_ARG_PREFIX + "run",
    63                 CMDLINE_ARG_PREFIX + "run",
    58                 arg -> flushTestGroup(getJavaMethodsFromArg(arg).map(
    64                 arg -> addTestGroup(getJavaMethodsFromArg(arg).map(
    59                         TestBuilder::toMethodCalls).flatMap(List::stream).collect(
    65                         ThrowingFunction.toFunction(
       
    66                                 TestBuilder::toMethodCalls)).flatMap(s -> s).collect(
    60                         Collectors.toList())),
    67                         Collectors.toList())),
       
    68 
       
    69                 CMDLINE_ARG_PREFIX + "exclude",
       
    70                 arg -> (excludedTests = Optional.ofNullable(
       
    71                         excludedTests).orElse(new HashSet<String>())).add(arg),
       
    72 
       
    73                 CMDLINE_ARG_PREFIX + "include",
       
    74                 arg -> (includedTests = Optional.ofNullable(
       
    75                         includedTests).orElse(new HashSet<String>())).add(arg),
       
    76 
       
    77                 CMDLINE_ARG_PREFIX + "space-subst",
       
    78                 arg -> spaceSubstitute = arg,
       
    79 
       
    80                 CMDLINE_ARG_PREFIX + "group",
       
    81                 arg -> flushTestGroup(),
       
    82 
    61                 CMDLINE_ARG_PREFIX + "dry-run",
    83                 CMDLINE_ARG_PREFIX + "dry-run",
    62                 arg -> dryRun = true);
    84                 arg -> dryRun = true
       
    85         );
    63         this.testConsumer = testConsumer;
    86         this.testConsumer = testConsumer;
    64         clear();
    87         clear();
    65     }
    88     }
    66 
    89 
    67     void processCmdLineArg(String arg) throws Throwable {
    90     void processCmdLineArg(String arg) throws Throwable {
    85             ex.setContext(arg);
   108             ex.setContext(arg);
    86             throw ex;
   109             throw ex;
    87         }
   110         }
    88     }
   111     }
    89 
   112 
    90     private void flushTestGroup(List<MethodCall> newTestGroup) {
   113     private void addTestGroup(List<MethodCall> newTestGroup) {
    91         if (testGroup != null) {
   114         if (testGroup != null) {
    92             testGroup.forEach(testBody -> createTestInstance(testBody));
   115             testGroup.addAll(newTestGroup);
       
   116         } else {
       
   117             testGroup = newTestGroup;
       
   118         }
       
   119     }
       
   120 
       
   121     private static Stream<MethodCall> filterTests(Stream<MethodCall> tests,
       
   122             Set<String> filters, UnaryOperator<Boolean> pred, String logMsg) {
       
   123         if (filters == null) {
       
   124             return tests;
       
   125         }
       
   126 
       
   127         // Log all matches before returning from the function
       
   128         return tests.filter(test -> {
       
   129             String testDescription = test.createDescription().testFullName();
       
   130             boolean match = filters.stream().anyMatch(
       
   131                     v -> testDescription.contains(v));
       
   132             if (match) {
       
   133                 trace(String.format(logMsg + ": %s", testDescription));
       
   134             }
       
   135             return pred.apply(match);
       
   136         }).collect(Collectors.toList()).stream();
       
   137     }
       
   138 
       
   139     private Stream<MethodCall> filterTestGroup() {
       
   140         Objects.requireNonNull(testGroup);
       
   141 
       
   142         UnaryOperator<Set<String>> restoreSpaces = filters -> {
       
   143             if (spaceSubstitute == null || filters == null) {
       
   144                 return filters;
       
   145             }
       
   146             return filters.stream().map(
       
   147                     filter -> filter.replace(spaceSubstitute, " ")).collect(
       
   148                             Collectors.toSet());
       
   149         };
       
   150 
       
   151         if (includedTests != null) {
       
   152             return filterTests(testGroup.stream(), restoreSpaces.apply(
       
   153                     includedTests), x -> x, "Include");
       
   154         }
       
   155 
       
   156         return filterTests(testGroup.stream(),
       
   157                 restoreSpaces.apply(excludedTests), x -> !x, "Exclude");
       
   158     }
       
   159 
       
   160     private void flushTestGroup() {
       
   161         if (testGroup != null) {
       
   162             filterTestGroup().forEach(testBody -> createTestInstance(testBody));
    93             clear();
   163             clear();
    94         }
   164         }
    95         testGroup = newTestGroup;
       
    96     }
   165     }
    97 
   166 
    98     private void createTestInstance(MethodCall testBody) {
   167     private void createTestInstance(MethodCall testBody) {
    99         ThrowingFunction<MethodCall, Object> testContructor;
   168         final List<ThrowingConsumer> curBeforeActions;
   100         if (dryRun) {
   169         final List<ThrowingConsumer> curAfterActions;
   101             testContructor = (unused) -> null;
   170 
   102             testBody = DRY_RUN_TEST_BODY;
   171         Method testMethod = testBody.getMethod();
       
   172         if (Stream.of(BeforeEach.class, AfterEach.class).anyMatch(
       
   173                 type -> testMethod.isAnnotationPresent(type))) {
       
   174             curBeforeActions = beforeActions;
       
   175             curAfterActions = afterActions;
   103         } else {
   176         } else {
   104             testContructor = TestBuilder::constructTest;
   177             curBeforeActions = new ArrayList<>(beforeActions);
   105         }
   178             curAfterActions = new ArrayList<>(afterActions);
   106 
   179 
   107         TestInstance test = new TestInstance(testContructor, testBody,
   180             selectFrameMethods(testMethod.getDeclaringClass(), BeforeEach.class).map(
   108                 beforeActions, afterActions);
   181                     this::wrap).forEachOrdered(curBeforeActions::add);
   109         trace(String.format("[%s] test constructed", test.fullName()));
   182             selectFrameMethods(testMethod.getDeclaringClass(), AfterEach.class).map(
       
   183                     this::wrap).forEachOrdered(curAfterActions::add);
       
   184         }
       
   185 
       
   186         TestInstance test = new TestInstance(testBody, curBeforeActions,
       
   187                 curAfterActions, dryRun);
       
   188         if (includedTests == null) {
       
   189             trace(String.format("Create: %s", test.fullName()));
       
   190         }
   110         testConsumer.accept(test);
   191         testConsumer.accept(test);
   111     }
       
   112 
       
   113     public static void nop () {
       
   114     }
       
   115 
       
   116     private final static MethodCall DRY_RUN_TEST_BODY = ThrowingSupplier.toSupplier(() -> {
       
   117         return new MethodCall(TestBuilder.class.getMethod("nop"));
       
   118     }).get();
       
   119 
       
   120     private static Object constructTest(MethodCall testBody) throws
       
   121             NoSuchMethodException, InstantiationException,
       
   122             IllegalAccessException, IllegalArgumentException,
       
   123             InvocationTargetException {
       
   124         Constructor ctor = testBody.getRequiredConstructor();
       
   125         if (ctor == null) {
       
   126             return null;
       
   127         }
       
   128         return ctor.newInstance();
       
   129     }
   192     }
   130 
   193 
   131     private void clear() {
   194     private void clear() {
   132         beforeActions = new ArrayList<>();
   195         beforeActions = new ArrayList<>();
   133         afterActions = new ArrayList<>();
   196         afterActions = new ArrayList<>();
       
   197         excludedTests = null;
       
   198         includedTests = null;
       
   199         spaceSubstitute = null;
   134         testGroup = null;
   200         testGroup = null;
   135     }
   201     }
   136 
   202 
   137     private static Class probeClass(String name) {
   203     private static Class probeClass(String name) {
   138         try {
   204         try {
   139             return Class.forName(name);
   205             return Class.forName(name);
   140         } catch (ClassNotFoundException ex) {
   206         } catch (ClassNotFoundException ex) {
   141             return null;
   207             return null;
   142         }
   208         }
       
   209     }
       
   210 
       
   211     private static Stream<Method> selectFrameMethods(Class type, Class annotationType) {
       
   212         return Stream.of(type.getMethods())
       
   213                 .filter(m -> m.getParameterCount() == 0)
       
   214                 .filter(m -> !m.isAnnotationPresent(Test.class))
       
   215                 .filter(m -> m.isAnnotationPresent(annotationType))
       
   216                 .sorted((a, b) -> a.getName().compareTo(b.getName()));
   143     }
   217     }
   144 
   218 
   145     private static Stream<String> cmdLineArgValueToMethodNames(String v) {
   219     private static Stream<String> cmdLineArgValueToMethodNames(String v) {
   146         List<String> result = new ArrayList<>();
   220         List<String> result = new ArrayList<>();
   147         String defaultClassName = null;
   221         String defaultClassName = null;
   154                 defaultClassName = token;
   228                 defaultClassName = token;
   155                 Stream.of(testSet.getMethods()).filter(
   229                 Stream.of(testSet.getMethods()).filter(
   156                         m -> m.isAnnotationPresent(Test.class)).map(
   230                         m -> m.isAnnotationPresent(Test.class)).map(
   157                                 Method::getName).distinct().forEach(
   231                                 Method::getName).distinct().forEach(
   158                                 name -> result.add(String.join(".", token, name)));
   232                                 name -> result.add(String.join(".", token, name)));
       
   233 
   159                 continue;
   234                 continue;
   160             }
   235             }
   161 
   236 
   162             final String qualifiedMethodName;
   237             final String qualifiedMethodName;
   163             final int lastDotIdx = token.lastIndexOf('.');
   238             final int lastDotIdx = token.lastIndexOf('.');
   186         }
   261         }
   187         return false;
   262         return false;
   188     }
   263     }
   189 
   264 
   190     private static boolean isParametrized(Method method) {
   265     private static boolean isParametrized(Method method) {
   191         return method.isAnnotationPresent(Parameters.class) || method.isAnnotationPresent(
   266         return method.isAnnotationPresent(ParameterGroup.class) || method.isAnnotationPresent(
   192                 Parameter.class);
   267                 Parameter.class);
   193     }
   268     }
   194 
   269 
   195     private static List<Method> getJavaMethodFromString(
   270     private static List<Method> getJavaMethodFromString(
   196             String qualifiedMethodName) {
   271             String qualifiedMethodName) {
   213         if (methods.isEmpty()) {
   288         if (methods.isEmpty()) {
   214             new ParseException(String.format(
   289             new ParseException(String.format(
   215                     "Method [%s] not found in [%s] class;",
   290                     "Method [%s] not found in [%s] class;",
   216                     methodName, className));
   291                     methodName, className));
   217         }
   292         }
   218         // Make sure default constructor is accessible if the one is needed.
       
   219         // Need to probe all methods as some of them might be static and
       
   220         // some class members.
       
   221         // Onlu class members require default ctor.
       
   222         for (Method method : methods) {
       
   223             try {
       
   224                 MethodCall.getRequiredConstructor(method);
       
   225             } catch (NoSuchMethodException ex) {
       
   226                 throw new ParseException(String.format(
       
   227                         "Default constructor not found in [%s] class;",
       
   228                         className));
       
   229             }
       
   230         }
       
   231 
   293 
   232         trace(String.format("%s -> %s", qualifiedMethodName, methods));
   294         trace(String.format("%s -> %s", qualifiedMethodName, methods));
   233         return methods;
   295         return methods;
   234     }
   296     }
   235 
   297 
   238                 ThrowingFunction.toFunction(
   300                 ThrowingFunction.toFunction(
   239                         TestBuilder::getJavaMethodFromString)).flatMap(
   301                         TestBuilder::getJavaMethodFromString)).flatMap(
   240                         List::stream).sequential();
   302                         List::stream).sequential();
   241     }
   303     }
   242 
   304 
   243     private static Parameter[] getParameters(Method method) {
   305     private static Parameter[] getMethodParameters(Method method) {
   244         if (method.isAnnotationPresent(Parameters.class)) {
   306         if (method.isAnnotationPresent(ParameterGroup.class)) {
   245             return ((Parameters) method.getAnnotation(Parameters.class)).value();
   307             return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value();
   246         }
   308         }
   247 
   309 
   248         if (method.isAnnotationPresent(Parameter.class)) {
   310         if (method.isAnnotationPresent(Parameter.class)) {
   249             return new Parameter[]{(Parameter) method.getAnnotation(
   311             return new Parameter[]{(Parameter) method.getAnnotation(
   250                 Parameter.class)};
   312                 Parameter.class)};
   252 
   314 
   253         // Unexpected
   315         // Unexpected
   254         return null;
   316         return null;
   255     }
   317     }
   256 
   318 
   257     private static List<MethodCall> toMethodCalls(Method method) {
   319     private static Stream<Object[]> toCtorArgs(Method method) throws
       
   320             IllegalAccessException, InvocationTargetException {
       
   321         Class type = method.getDeclaringClass();
       
   322         List<Method> paremetersProviders = Stream.of(type.getMethods())
       
   323                 .filter(m -> m.getParameterCount() == 0)
       
   324                 .filter(m -> (m.getModifiers() & Modifier.STATIC) != 0)
       
   325                 .filter(m -> m.isAnnotationPresent(Parameters.class))
       
   326                 .sorted()
       
   327                 .collect(Collectors.toList());
       
   328         if (paremetersProviders.isEmpty()) {
       
   329             // Single instance using the default constructor.
       
   330             return Stream.ofNullable(MethodCall.DEFAULT_CTOR_ARGS);
       
   331         }
       
   332 
       
   333         // Pick the first method from the list.
       
   334         Method paremetersProvider = paremetersProviders.iterator().next();
       
   335         if (paremetersProviders.size() > 1) {
       
   336             trace(String.format(
       
   337                     "Found %d public static methods without arguments with %s annotation. Will use %s",
       
   338                     paremetersProviders.size(), Parameters.class,
       
   339                     paremetersProvider));
       
   340             paremetersProviders.stream().map(Method::toString).forEach(
       
   341                     TestBuilder::trace);
       
   342         }
       
   343 
       
   344         // Construct collection of arguments for test class instances.
       
   345         return ((Collection) paremetersProvider.invoke(null)).stream();
       
   346     }
       
   347 
       
   348     private static Stream<MethodCall> toMethodCalls(Method method) throws
       
   349             IllegalAccessException, InvocationTargetException {
       
   350         return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(
       
   351                 s -> s).peek(methodCall -> {
       
   352                     // Make sure required constructor is accessible if the one is needed.
       
   353                     // Need to probe all methods as some of them might be static
       
   354                     // and some class members.
       
   355                     // Only class members require ctors.
       
   356                     try {
       
   357                         methodCall.checkRequiredConstructor();
       
   358                     } catch (NoSuchMethodException ex) {
       
   359                         throw new ParseException(ex.getMessage() + ".");
       
   360                     }
       
   361                 });
       
   362     }
       
   363 
       
   364     private static Stream<MethodCall> toMethodCalls(Object[] ctorArgs, Method method) {
   258         if (!isParametrized(method)) {
   365         if (!isParametrized(method)) {
   259             return List.of(new MethodCall(method));
   366             return Stream.of(new MethodCall(ctorArgs, method));
   260         }
   367         }
   261         Parameter[] annotations = getParameters(method);
   368         Parameter[] annotations = getMethodParameters(method);
   262         if (annotations.length == 0) {
   369         if (annotations.length == 0) {
   263             return List.of(new MethodCall(method));
   370             return Stream.of(new MethodCall(ctorArgs, method));
   264         }
   371         }
   265         return Stream.of(annotations).map((a) -> {
   372         return Stream.of(annotations).map((a) -> {
   266             String annotationValue = a.value();
   373             Class paramType = method.getParameterTypes()[0];
   267             Class paramClass = method.getParameterTypes()[0];
   374             final Object annotationValue;
   268             return new MethodCall(method,
   375             if (!paramType.isArray()) {
   269                     fromString(annotationValue, paramClass));
   376                 annotationValue = fromString(a.value()[0], paramType);
   270         }).collect(Collectors.toList());
   377             } else {
       
   378                 Class paramComponentType = paramType.getComponentType();
       
   379                 annotationValue = Array.newInstance(paramComponentType, a.value().length);
       
   380                 var idx = new AtomicInteger(-1);
       
   381                 Stream.of(a.value()).map(v -> fromString(v, paramComponentType)).sequential().forEach(
       
   382                         v -> Array.set(annotationValue, idx.incrementAndGet(), v));
       
   383             }
       
   384             return new MethodCall(ctorArgs, method, annotationValue);
       
   385         });
   271     }
   386     }
   272 
   387 
   273     private static Object fromString(String value, Class toType) {
   388     private static Object fromString(String value, Class toType) {
   274         Function<String, Object> converter = conv.get(toType);
   389         Function<String, Object> converter = conv.get(toType);
   275         if (converter == null) {
   390         if (converter == null) {
   279         }
   394         }
   280         return converter.apply(value);
   395         return converter.apply(value);
   281     }
   396     }
   282 
   397 
   283     // Wraps Method.invike() into ThrowingRunnable.run()
   398     // Wraps Method.invike() into ThrowingRunnable.run()
   284     private static ThrowingConsumer wrap(Method method, boolean dryRun) {
   399     private ThrowingConsumer wrap(Method method) {
   285         return (test) -> {
   400         return (test) -> {
   286             Class methodClass = method.getDeclaringClass();
   401             Class methodClass = method.getDeclaringClass();
   287             String methodName = String.join(".", methodClass.getName(),
   402             String methodName = String.join(".", methodClass.getName(),
   288                     method.getName());
   403                     method.getName());
   289             TKit.log(String.format("[ CALL     ] %s()", methodName));
   404             TKit.log(String.format("[ CALL     ] %s()", methodName));
   316             return msg;
   431             return msg;
   317         }
   432         }
   318         private String badCmdLineArg;
   433         private String badCmdLineArg;
   319     }
   434     }
   320 
   435 
   321     private static void trace(String msg) {
   436     static void trace(String msg) {
   322         if (TKit.VERBOSE_TEST_SETUP) {
   437         if (TKit.VERBOSE_TEST_SETUP) {
   323             TKit.log(msg);
   438             TKit.log(msg);
   324         }
   439         }
   325     }
   440     }
   326 
   441 
   327     private final Map<String, ThrowingConsumer<String>> argProcessors;
   442     private final Map<String, ThrowingConsumer<String>> argProcessors;
   328     private Consumer<TestInstance> testConsumer;
   443     private Consumer<TestInstance> testConsumer;
   329     private List<MethodCall> testGroup;
   444     private List<MethodCall> testGroup;
   330     private List<ThrowingConsumer> beforeActions;
   445     private List<ThrowingConsumer> beforeActions;
   331     private List<ThrowingConsumer> afterActions;
   446     private List<ThrowingConsumer> afterActions;
       
   447     private Set<String> excludedTests;
       
   448     private Set<String> includedTests;
       
   449     private String spaceSubstitute;
   332     private boolean dryRun;
   450     private boolean dryRun;
   333 
   451 
   334     private final static Map<Class, Function<String, Object>> conv = Map.of(
   452     private final static Map<Class, Function<String, Object>> conv = Map.of(
   335             boolean.class, Boolean::valueOf,
   453             boolean.class, Boolean::valueOf,
   336             Boolean.class, Boolean::valueOf,
   454             Boolean.class, Boolean::valueOf,