All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dakusui.jcunit.core.JCUnit Maven / Gradle / Ivy

package com.github.dakusui.jcunit.core;

import com.github.dakusui.jcunit.constraint.ConstraintManager;
import com.github.dakusui.jcunit.core.factor.Factors;
import com.github.dakusui.jcunit.core.tuples.Tuple;
import com.github.dakusui.jcunit.exceptions.JCUnitUserException;
import com.github.dakusui.jcunit.generators.TupleGenerator;
import com.github.dakusui.jcunit.generators.TupleGeneratorFactory;
import org.junit.runner.Runner;
import org.junit.runners.Suite;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

public class JCUnit extends Suite {
  private final ArrayList runners = new ArrayList();

  /**
   * Only called reflectively by JUnit. Do not use programmatically.
   */
  public JCUnit(Class klass) throws Throwable {
    super(klass, Collections.emptyList());
    try {
      ////
      // Prepare filter method(s) and custom test case methods.
      List frameworkMethodFailures = new LinkedList();
      List preconditionMethods = getFrameworkMethods(frameworkMethodFailures, FrameworkMethodValidator.PRECONDITION);
      // Currently only one filter method can be used.
      // Custom test case methods.
      List customTestCaseMethods = getFrameworkMethods(frameworkMethodFailures, FrameworkMethodValidator.CUSTOM_TESTCASES);
      ////
      // Check if any error was found.
      ConfigUtils.checkEnv(frameworkMethodFailures.isEmpty(),
          "Errors are found in test class '%s':%s",
          getTestClass().getJavaClass().getCanonicalName(),
          frameworkMethodFailures);

      ////
      // Generate a list of test cases using a specified tuple generator
      TupleGenerator tupleGenerator = TupleGeneratorFactory.INSTANCE
          .createTupleGeneratorFromClass(klass);
      Factors factors = tupleGenerator.getFactors();
      int id;
      for (id = (int) tupleGenerator.firstId();
           id >= 0; id = (int) tupleGenerator.nextId(id)) {
        Tuple testCase = tupleGenerator.get(id);
        if (shouldPerform(testCase, preconditionMethods)) {
          runners.add(new JCUnitRunner(getTestClass().getJavaClass(),
              id, TestCaseType.Generated,
              factors,
              testCase));
        }
      }
      // Skip to number of test cases generated.
      id = (int) tupleGenerator.size();
      ////
      // Compose a list of 'negative test cases' and register them.
      ConstraintManager cm = tupleGenerator.getConstraintManager();
      final List violations = cm.getViolations();
      id = registerTestCases(
          id,
          factors,
          violations,
          TestCaseType.Violation,
          preconditionMethods);
      ////
      // Compose a list of 'custom test cases' and register them.
      registerTestCases(
          id,
          factors,
          invokeCustomTestCasesMethod(customTestCaseMethods),
          TestCaseType.Custom,
          preconditionMethods);
      ConfigUtils.checkEnv(runners.size() > 0, "No test to be run was found.");
    } catch (JCUnitUserException e) {
      e.setTargetClass(klass);
      throw e;
    }
  }

  static Object createTest(TestClass testClass, Tuple testCase) {
    return TestCaseUtils.toTestObject(testClass.getJavaClass(), testCase);
  }

  private boolean shouldPerform(Tuple testCase, List preconditionMethods) {
    if (preconditionMethods.isEmpty()) return true;
    for (FrameworkMethod m : preconditionMethods) {
      try {
        Object testObject = createTest(this.getTestClass(), testCase);
        if ((Boolean)m.invokeExplosively(null, testObject)) return true;
      } catch (Throwable throwable) {
        ConfigUtils.rethrow(throwable, "Failed to execute ");
      }
    }
    return false;
  }

  private int registerTestCases(int id,
      Factors factors,
      Iterable testCases,
      TestCaseType testCaseType,
      List preconditionMethods)
      throws Throwable {
    for (Tuple testCase : testCases) {
      if (shouldPerform(testCase, preconditionMethods)) {
        runners.add(new JCUnitRunner(
            getTestClass().getJavaClass(),
            id,
            testCaseType,
            factors,
            testCase));
      }
      id++;
    }
    return id;
  }

  @Override
  protected List getChildren() {
    return runners;
  }

  private List invokeCustomTestCasesMethod(List customTestCasesMethods) {
    List ret = new LinkedList();
    try {
      for (FrameworkMethod m : customTestCasesMethods) {
        Object r = m.invokeExplosively(null);

        if (r instanceof Tuple) {
          ret.add((Tuple) r);
        } else if (r instanceof Iterable) {
          for (Object o : (Iterable) r) {
            if (o == null) {
              ConfigUtils.checkEnv(false, "Returned value of '%s' must not contain null.", m.getName());
            }
            if (o instanceof Tuple) {
              ret.add((Tuple) o);
            } else if (getTestClass().getJavaClass().isAssignableFrom(o.getClass())) {
              ret.add(TestCaseUtils.toTestCase(o));
            } else {
              ConfigUtils.checkEnv(false, "Returned value of '%s' must contain only Tuple or test objects.", m.getName());
            }
          }
        } else {
          Utils.checkcond(false);
        }
      }
    } catch (Throwable throwable) {
      Utils.rethrow(throwable, "Failed to execute '%s'.: (%s)", throwable.getMessage());
    }
    return ret;
  }

  private List getFrameworkMethods(List failures, FrameworkMethodValidator validator) {
    Class annClass = validator.getAnnotation();
    List methods = getTestClass().getAnnotatedMethods(annClass);
    List ret = new ArrayList(methods.size());
    for (FrameworkMethod m : methods) {
      if (validator.validate(getTestClass().getJavaClass(), m)) {
        ret.add(m);
      } else {
        failures.add(
            String.format(
                "Method '%s'(annotated by '%s') must satisfy the following conditions: %s",
                m.getName(),
                annClass.getName(),
                validator.getDescription()
            ));
      }
    }
    return ret;
  }

  static void validateTestPreconditionMethod(Class testClass, FrameworkMethod method, List failures) {
    if (!method.isPublic()) {
      failures.add(String.format(
          "The method '%s' must be public. (in %s)", method.getName(), testClass.getCanonicalName()
      ));
    }
    if (!method.isStatic()) {
      failures.add(String.format(
          "The method '%s' must be static. (in %s)", method.getName(), testClass.getCanonicalName()
      ));
    }
    if (Boolean.class.equals(method.getReturnType()) && method.getReturnType().isPrimitive()) {
      failures.add(String.format(
          "The method '%s' must return a boolean value. (in %s)", method.getName(), testClass.getCanonicalName()
      ));
    }
  }

  /**
   * Returns a {@code Method} object or {@code null} if the specified method is not found or not loadable.
   */
  static Method getTestPreconditionMethod(Class testClass, String methodName, List failures) {
    try {
      return testClass.getMethod(methodName, testClass);
    } catch (NoSuchMethodException e) {
      failures.add(String.format(
          "The method '%s(%s)' can't be found in the test class '%s'.",
          methodName,
          testClass,
          testClass.getName()
      ));
      return null;
    }
  }


  public interface FrameworkMethodValidator {
    public static final FrameworkMethodValidator CUSTOM_TESTCASES = new FrameworkMethodValidator() {
      @Override
      public boolean validate(Class testClass, FrameworkMethod m) {
        Method mm = m.getMethod();
        return m.isPublic() && m.isStatic() && mm.getParameterTypes().length == 0 &&
            (Tuple.class.isAssignableFrom(mm.getReturnType()) ||
                (Iterable.class.isAssignableFrom(mm.getReturnType())
                ));
      }

      @Override
      public Class getAnnotation() {
        return CustomTestCases.class;
      }

      @Override
      public String getDescription() {
        return "public, static, no parameter, and returning 'LabeledTestCase' or an iterable of it";
      }
    };

    public static final FrameworkMethodValidator PRECONDITION = new FrameworkMethodValidator() {
      @Override
      public boolean validate(Class testClass, FrameworkMethod m) {
        Method mm = m.getMethod();
        boolean ret;
        ret = mm.getParameterTypes().length == 1;
        ret &= mm.getParameterTypes()[0].isAssignableFrom(testClass);
        List failures = new LinkedList();
        validateTestPreconditionMethod(m.getDeclaringClass(), m, failures);
        ret &= failures.isEmpty();
        return ret;

      }

      @Override
      public Class getAnnotation() {
        return Precondition.class;
      }

      @Override
      public String getDescription() {
        return "public, static, returning boolean, accepting an object of the class in which it is declared as the first parameter";
      }
    };

    public boolean validate(Class testClass, FrameworkMethod m);

    public Class getAnnotation();

    public String getDescription();
  }

  public static class TestCaseInternalAnnotation implements Annotation {

    private final TestCaseType type;
    private final int          id;
    private       Factors      factors;
    private       Tuple        testCase;

    public TestCaseInternalAnnotation(TestCaseType type, int id, Factors factors, Tuple testCase) {
      Utils.checknotnull(type);
      this.id = id;
      this.type = type;
      this.factors = factors;
      this.testCase = testCase;
    }

    @Override
    public Class annotationType() {
      return this.getClass();
    }

    public int getId() {
      return this.id;
    }

    public TestCaseType getTestCaseType() {
      return this.type;
    }

    public Tuple getTestCase() {
      return testCase;
    }

    public Factors getFactors() {
      return factors;
    }
  }

  public static enum TestCaseType {
    Custom,
    Generated,
    Violation
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy