NodeUtils.java

  1. package com.github.dakusui.jcunitx.runners.core;

  2. import com.github.dakusui.jcunitx.core.AArray;
  3. import com.github.dakusui.jcunitx.exceptions.FrameworkException;
  4. import com.github.dakusui.jcunitx.factorspace.Constraint;
  5. import com.github.dakusui.jcunitx.runners.junit4.annotations.Condition;
  6. import com.github.dakusui.jcunitx.runners.junit4.annotations.ConfigureWith;
  7. import com.github.dakusui.jcunitx.runners.junit4.annotations.From;
  8. import org.junit.runners.model.FrameworkMethod;
  9. import org.junit.runners.model.TestClass;

  10. import java.lang.reflect.Method;
  11. import java.util.*;
  12. import java.util.concurrent.atomic.AtomicInteger;
  13. import java.util.function.Function;
  14. import java.util.function.Predicate;
  15. import java.util.stream.Collectors;
  16. import java.util.stream.Stream;

  17. import static com.github.dakusui.jcunitx.utils.Utils.createInstanceOf;
  18. import static com.github.dakusui.jcunitx.exceptions.FrameworkException.unexpectedByDesign;
  19. import static java.util.stream.Collectors.toList;

  20. public enum NodeUtils {
  21.   ;

  22.   public static TestInputPredicate buildPredicate(String[] values, SortedMap<String, TestInputPredicate> predicates_) {
  23.     class Builder implements Node.Visitor {
  24.       private final SortedMap<String, TestInputPredicate> predicates   = predicates_;
  25.       private Predicate<AArray>                           result;
  26.       private final SortedSet<String>                     involvedKeys = new TreeSet<>();

  27.       @Override
  28.       public void visitLeaf(Node.Leaf leaf) {
  29.         TestInputPredicate predicate = lookupTestPredicate(leaf.id()).orElseThrow(FrameworkException::unexpectedByDesign);
  30.         involvedKeys.addAll(predicate.involvedKeys());
  31.         if (leaf.args().length == 0)
  32.           result = predicate;
  33.         else
  34.           result = tuple -> predicate.test(appendArgs(tuple, leaf));
  35.       }

  36.       private AArray appendArgs(AArray tuple, Node.Leaf leaf) {
  37.         return new AArray.Builder() {{
  38.           putAll(tuple);
  39.           for (int i = 0; i < leaf.args().length; i++) {
  40.             put(String.format("@arg[%s]", i), expandFactorValueIfNecessary(tuple, leaf.args()[i]));
  41.           }
  42.         }}.build();
  43.       }

  44.       private Object expandFactorValueIfNecessary(AArray tuple, String arg) {
  45.         if (arg.startsWith("@"))
  46.           return tuple.get(arg.substring(1));
  47.         return arg;
  48.       }

  49.       @Override
  50.       public void visitAnd(Node.And and) {
  51.         and.children().forEach(
  52.             node -> {
  53.               Predicate<AArray> previous = result;
  54.               node.accept(this);
  55.               if (previous != null) {
  56.                 result = previous.and(result);
  57.               }
  58.             });
  59.       }

  60.       @Override
  61.       public void visitOr(Node.Or or) {
  62.         or.children().forEach(
  63.             node -> {
  64.               Predicate<AArray> previous = result;
  65.               node.accept(this);
  66.               if (previous != null) {
  67.                 result = previous.or(result);
  68.               }
  69.             });
  70.       }

  71.       @Override
  72.       public void visitNot(Node.Not not) {
  73.         not.target().accept(this);
  74.         result = result.negate();
  75.       }

  76.       private Optional<TestInputPredicate> lookupTestPredicate(String name) {
  77.         return this.predicates.containsKey(name) ?
  78.             Optional.of(this.predicates.get(name)) :
  79.             Optional.empty();
  80.       }
  81.     }
  82.     Builder builder = new Builder();
  83.     parse(values).accept(builder);
  84.     return TestInputPredicate.of(
  85.         Arrays.toString(values),
  86.         new ArrayList<>(builder.involvedKeys),
  87.         builder.result
  88.     );
  89.   }

  90.   public static List<String> allLeaves(String[] values) {
  91.     return new LinkedList<String>() {
  92.       {
  93.         parse(values).accept(
  94.             new Node.Visitor.Base() {
  95.               @Override
  96.               public void visitLeaf(Node.Leaf leaf) {
  97.                 add(leaf.id());
  98.               }
  99.             });
  100.       }
  101.     };
  102.   }

  103.   public static SortedMap<String, TestInputPredicate> allTestPredicates(TestClass testClass) {
  104.     ////
  105.     // TestClass <>--------------> parameterSpace class
  106.     //                               constraints
  107.     //   non-constraint-condition    non-constraint-condition?
  108.     // TestClass
  109.     //   constraints
  110.     //   non-constraint-condition
  111.     return new TreeMap<>((Objects.equals(testClass.getJavaClass(), getParameterSpaceDefinitionClass(testClass)) ?
  112.         streamTestPredicatesIn(testClass.getJavaClass()) :
  113.         Stream.concat(
  114.             streamTestPredicatesIn(getParameterSpaceDefinitionClass(testClass)),
  115.             streamTestPredicatesIn(testClass.getJavaClass()).filter(
  116.                 each -> !(each instanceof Constraint)
  117.             )
  118.         )
  119.     ).collect(Collectors.toMap(
  120.         TestInputPredicate::getName,
  121.         each -> each
  122.     )));
  123.   }

  124.   private static Class<?> getParameterSpaceDefinitionClass(TestClass testClass) {
  125.     ConfigureWith configureWith = testClass.getAnnotation(ConfigureWith.class);
  126.     configureWith = configureWith == null ?
  127.         ConfigureWith.DEFAULT_INSTANCE :
  128.         configureWith;
  129.     return Objects.equals(configureWith.parameterSpace(), Object.class) ?
  130.         testClass.getJavaClass() :
  131.         configureWith.parameterSpace();
  132.   }

  133.   private static Stream<TestInputPredicate> streamTestPredicatesIn(Class<?> parameterSpaceDefinitionClass) {
  134.     TestClass wrapper = new TestClass(parameterSpaceDefinitionClass);
  135.     Object testObject = createInstanceOf(wrapper);
  136.     return wrapper.getAnnotatedMethods(Condition.class).stream(
  137.     ).map(
  138.         frameworkMethod -> createTestPredicate(testObject, frameworkMethod)
  139.     );
  140.   }

  141.   public static TestInputPredicate createTestPredicate(Object testObject, FrameworkMethod frameworkMethod) {
  142.     Method method = frameworkMethod.getMethod();
  143.     //noinspection RedundantTypeArguments (to suppress a compilation error)
  144.     List<String> involvedKeys = Stream.of(method.getParameterAnnotations())
  145.         .map(annotations -> Stream.of(annotations)
  146.             .filter(annotation -> annotation instanceof From)
  147.             .map(From.class::cast)
  148.             .map(From::value)
  149.             .findFirst()
  150.             .<FrameworkException>orElseThrow(FrameworkException::unexpectedByDesign))
  151.         .collect(toList());
  152.     int varargsIndex = method.isVarArgs() ?
  153.         frameworkMethod.getMethod().getParameterCount() - 1 :
  154.         -1;
  155.     Predicate<AArray> predicate = (AArray tuple) -> {
  156.       try {
  157.         return (boolean) frameworkMethod.invokeExplosively(
  158.             testObject,
  159.             involvedKeys.stream()
  160.                 .map(new Function<String, Object>() {
  161.                   final AtomicInteger cur = new AtomicInteger(0);

  162.                   @Override
  163.                   public Object apply(String key) {
  164.                     if (key.equals("@arg"))
  165.                       return isVarArgs(cur.get()) ?
  166.                           getVarArgs() :
  167.                           getArg();
  168.                     return tuple.get(key);
  169.                   }

  170.                   private Object getArg() {
  171.                     return tuple.get(key(cur.getAndIncrement()));
  172.                   }

  173.                   private Object getVarArgs() {
  174.                     List<Object> work = new LinkedList<>();
  175.                     while (tuple.containsKey(key(cur.get()))) {
  176.                       work.add(getArg());
  177.                     }
  178.                     return work.toArray();
  179.                   }

  180.                   private boolean isVarArgs(int argIndex) {
  181.                     return argIndex == varargsIndex;
  182.                   }

  183.                   private String key(int i) {
  184.                     return String.format("@arg[%d]", i);
  185.                   }
  186.                 })
  187.                 .toArray());
  188.       } catch (Throwable e) {
  189.         throw unexpectedByDesign(e);
  190.       }
  191.     };
  192.     return frameworkMethod.getAnnotation(Condition.class).constraint() ?
  193.         Constraint.create(frameworkMethod.getName(), predicate, involvedKeys) :
  194.         new TestInputPredicate() {
  195.           @Override
  196.           public String getName() {
  197.             return frameworkMethod.getName();
  198.           }

  199.           @Override
  200.           public boolean test(AArray tuple) {
  201.             return predicate.test(tuple);
  202.           }

  203.           @Override
  204.           public List<String> involvedKeys() {
  205.             return involvedKeys;
  206.           }
  207.         };
  208.   }

  209.   public static Node parse(String[] values) {
  210.     return new Node.Or.Impl(Stream.of(values)
  211.         .map(NodeUtils::parseLine)
  212.         .collect(toList()));
  213.   }

  214.   public static Node parseLine(String value) {
  215.     return new Node.And.Impl(
  216.         Stream.of(value.split("&&"))
  217.             .map(s -> s.startsWith("!") ?
  218.                 new Node.Not.Impl(new Node.Leaf.Impl(s.substring(1))) :
  219.                 new Node.Leaf.Impl(s)
  220.             )
  221.             .collect(toList())
  222.     );
  223.   }
  224. }