JUnit4Runner.java

package com.github.dakusui.jcunitx.runners.junit4;

import com.github.dakusui.jcunitx.utils.Checks;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.manipulation.Filter;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JUnit4Runner {
  static final int ALL_TESTCASES = -1;
  static final String ANY_METHOD = null;
  private final Request request;

  public Result run() {
    return new JUnitCore().run(this.getRequest());
  }

  public Request getRequest() {
    return this.request;
  }

  private JUnit4Runner(Class<?> clazz, String methodName, int startInclusive, int endExclusive) {
    this.request = Request.classes(clazz).filterWith(
        createFilter(methodName, startInclusive, endExclusive)
    );
  }

  private Filter createFilter(String methodName, int startInclusive, int endExclusive) {
    return new Filter() {
      @Override
      public boolean shouldRun(Description description) {
        if (description.isTest()) {
          return createPredicate(methodName, startInclusive, endExclusive).test(description);
        }

        // explicitly check if any children want to run
        for (Description each : description.getChildren()) {
          if (shouldRun(each)) {
            return true;
          }
        }
        return false;
      }

      @Override
      public String describe() {
        return String.format(
            "Method %s[%s]",
            methodName,
            startInclusive == ALL_TESTCASES ?
                "*" :
                String.format("%s-%s", startInclusive, endExclusive)
        );
      }
    };
  }

  private Predicate<Description> createPredicate(String methodName, int startInclusive, int endExclusive) {
    return description -> {
      Pattern pattern = Pattern.compile(
          Optional.ofNullable(methodName).orElse(".*") + "\\[([0-9]+)\\]"
      );
      Matcher matcher = pattern.matcher(description.getMethodName());

      if (!matcher.matches()) {
        return false;
      }

      int id = Integer.parseInt(matcher.group(1));
      return startInclusive == ALL_TESTCASES || id >= startInclusive && id < endExclusive;
    };
  }

  public static class Builder {

    private final Class<?> clazz;
    private String methodName     = ANY_METHOD;
    private int    startInclusive = ALL_TESTCASES;
    private int    endInclusive   = ALL_TESTCASES;

    public Builder(Class<?> clazz) {
      this.clazz = Objects.requireNonNull(clazz);
    }

    public Builder methodName(String methodName) {
      this.methodName = Objects.requireNonNull(methodName);
      return this;
    }

    public Builder allMethods() {
      this.methodName = ANY_METHOD;
      return this;
    }

    public Builder testCase(int testCaseId) {
      Checks.checkcond(testCaseId >= 0);
      this.startInclusive = testCaseId;
      this.endInclusive = testCaseId + 1;
      return this;
    }

    public Builder testCasesInRange(int startTestCaseId, int endTestCaseId) {
      Checks.checkcond(startTestCaseId >= 0);
      Checks.checkcond(endTestCaseId >= 0);
      Checks.checkcond(startTestCaseId <= endTestCaseId);
      this.startInclusive = startTestCaseId;
      this.endInclusive = endTestCaseId;
      return this;
    }

    public Builder testCasesFrom(int startInclusive) {
      Checks.checkcond(startInclusive >= 0);
      this.startInclusive = startInclusive;
      this.endInclusive = Integer.MAX_VALUE;
      return this;
    }

    public Builder testCasesUntil(int endInclusive) {
      Checks.checkcond(endInclusive >= 0);
      this.startInclusive = 0;
      this.endInclusive = endInclusive;
      return this;
    }

    public Builder allTestCases() {
      this.startInclusive = ALL_TESTCASES;
      return this;
    }

    public JUnit4Runner build() {
      return new JUnit4Runner(
          this.clazz,
          this.methodName,
          this.startInclusive,
          this.endInclusive
      );
    }
  }
}