content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package androidx.test.internal.runner;
  
  import androidx.annotation.VisibleForTesting;
  import java.lang.annotation.Annotation;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.Set;
  import org.junit.runner.Description;
  
  /**
   * This class represents a test size qualifier which can be used to filter out tests from a test
   * suite.
   *
   * <p>It quietly handles runner size filter annotations and old platform size annotations.
   */
  public final class TestSize {
  
    /** @see androidx.test.filters.SmallTest */
    public static final TestSize SMALL =
        new TestSize(
            "small",
            androidx.test.filters.SmallTest.class,
            "android.test.suitebuilder.annotation.SmallTest",
            200 /* in ms */);
  
    /** @see androidx.test.filters.MediumTest */
    public static final TestSize MEDIUM =
        new TestSize(
            "medium",
            androidx.test.filters.MediumTest.class,
            "android.test.suitebuilder.annotation.MediumTest",
            1000 /* in ms */);
  
    /** @see androidx.test.filters.LargeTest */
    public static final TestSize LARGE =
        new TestSize(
            "large",
            androidx.test.filters.LargeTest.class,
            "android.test.suitebuilder.annotation.LargeTest",
            Float.MAX_VALUE /* no threshold */);
  
    /**
     * A "null object" that is returned in case no test size matches, to avoid a null return value.
     */
    public static final TestSize NONE = new TestSize("", null, null, 0);
  
    private static final Set<TestSize> ALL_SIZES =
        Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SMALL, MEDIUM, LARGE)));
  
    private final String sizeQualifierName;
    private final Class<? extends Annotation> platformAnnotationClass;
    private final Class<? extends Annotation> runnerFilterAnnotationClass;
  
    /**
     * This value the maximum allowed runtime (in ms) for a test included in the test size suite. It
     * is used to make an educated guess at which size bucket a test belongs to.
     */
    private final float testSizeRunTimeThreshold;
  
    @VisibleForTesting
    public TestSize(
        String sizeQualifierName,
        Class<? extends Annotation> runnerFilterAnnotationClass,
        String legacyPlatformAnnotationClassName,
        float testSizeRuntimeThreshold) {
      this.sizeQualifierName = sizeQualifierName;
      this.platformAnnotationClass = loadPlatformAnnotationClass(legacyPlatformAnnotationClassName);
      this.runnerFilterAnnotationClass = runnerFilterAnnotationClass;
      testSizeRunTimeThreshold = testSizeRuntimeThreshold;
    }
  
    private static Class<? extends Annotation> loadPlatformAnnotationClass(
        String legacyPlatformAnnotationClassName) {
      if (legacyPlatformAnnotationClassName == null) {
        return null;
      }
      try {
        return (Class<? extends Annotation>) Class.forName(legacyPlatformAnnotationClassName);
      } catch (ClassNotFoundException e) {
        // ignore - not present on boot classpath
        return null;
      }
    }
  
    /** @return the test size name */
    public String getSizeQualifierName() {
      return sizeQualifierName;
    }
  
    /**
     * @return true if the test method in the {@link Description} is annotated with the test size
     *     annotation class.
     */
    public boolean testMethodIsAnnotatedWithTestSize(Description description) {
      if (description.getAnnotation(runnerFilterAnnotationClass) != null
          || description.getAnnotation(platformAnnotationClass) != null) {
        // If the test method is annotated with a test size annotation include it
        return true;
      }
      // Otherwise exclude it
      return false;
    }
  
    /**
     * @return true if the test class in the {@link Description} is annotated with the test size
     *     annotation.
     */
    public boolean testClassIsAnnotatedWithTestSize(Description description) {
      final Class<?> testClass = description.getTestClass();
      if (null == testClass) {
        return false;
      }
  
      if (hasAnnotation(testClass, runnerFilterAnnotationClass)
          || hasAnnotation(testClass, platformAnnotationClass)) {
        // If the test class is annotated with a test size annotation include it.
        return true;
      }
      return false;
    }
  
    private static boolean hasAnnotation(
        Class<?> testClass, Class<? extends Annotation> annotationClass) {
      return annotationClass != null && testClass.isAnnotationPresent(annotationClass);
    }
  
    /** @return the suite run time threshold for a given test size. */
    public float getRunTimeThreshold() {
      return testSizeRunTimeThreshold;
    }
  
    /**
     * Maps a runtime to a test size.
     *
     * @param testRuntime the runtime of the test
     * @return the test size which was mapped to the runtime
     */
    public static TestSize getTestSizeForRunTime(float testRuntime) {
      if (runTimeSmallerThanThreshold(testRuntime, SMALL.getRunTimeThreshold())) {
        return SMALL;
      } else if (runTimeSmallerThanThreshold(testRuntime, MEDIUM.getRunTimeThreshold())) {
        return MEDIUM;
      }
      return LARGE;
    }
  
    /**
     * @param annotationClass the test size annotation class
     * @return true if the the test size annotation is valid
     */
    public static boolean isAnyTestSize(Class<? extends Annotation> annotationClass) {
      for (TestSize testSize : ALL_SIZES) {
        if (testSize.getRunnerAnnotation() == annotationClass
            || testSize.getFrameworkAnnotation() == annotationClass) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Creates a test size instance from a test size string. This method will return {@link
     * TestSize#NONE} if the test size is unknown.
     */
    public static TestSize fromString(final String testSize) {
      TestSize testSizeFromString = NONE;
      for (TestSize testSizeValue : ALL_SIZES) {
        if (testSizeValue.getSizeQualifierName().equals(testSize)) {
          testSizeFromString = testSizeValue;
        }
      }
      return testSizeFromString;
    }
  
    /**
     * Creates a test size instance from a {@link Description}. This method will return {@link
     * TestSize#NONE} if the description does not contain any test size information.
     */
    public static TestSize fromDescription(Description description) {
      TestSize testSize = NONE;
      // Match on method level first
      for (TestSize testMethodSizeValue : ALL_SIZES) {
        if (testMethodSizeValue.testMethodIsAnnotatedWithTestSize(description)) {
          testSize = testMethodSizeValue;
          break;
        }
      }
      // If size annotation not matched on method level look at class level
      if (NONE.equals(testSize)) {
        for (TestSize testClassSizeValue : ALL_SIZES) {
          if (testClassSizeValue.testClassIsAnnotatedWithTestSize(description)) {
            testSize = testClassSizeValue;
            break;
          }
        }
      }
      return testSize;
    }
  
    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
  
      TestSize testSize = (TestSize) o;
  
      return sizeQualifierName.equals(testSize.sizeQualifierName);
    }
  
    @Override
    public int hashCode() {
      return sizeQualifierName.hashCode();
    }
  
    private static boolean runTimeSmallerThanThreshold(float testRuntime, float runtimeThreshold) {
      return Float.compare(testRuntime, runtimeThreshold) < 0;
    }
  
    private Class<? extends Annotation> getFrameworkAnnotation() {
      return platformAnnotationClass;
    }
  
    private Class<? extends Annotation> getRunnerAnnotation() {
      return runnerFilterAnnotationClass;
    }
  }
  