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

com.google.j2cl.junit.apt.JUnit4Validator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Google Inc.
 *
 * 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 com.google.j2cl.junit.apt;

import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.auto.common.MoreTypes;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.j2cl.junit.async.Timeout;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runners.Parameterized.AfterParam;
import org.junit.runners.Parameterized.BeforeParam;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

class JUnit4Validator extends BaseValidator {

  private final Types typeUtils;
  private final Elements elementUtils;

  public JUnit4Validator(ErrorReporter errorReporter, Types typeUtils, Elements elementUtils) {
    super(errorReporter);
    this.typeUtils = typeUtils;
    this.elementUtils = elementUtils;
  }

  public boolean validateJUnit4MethodAndClass(TypeElement typeElement) {
    if (!validateNotInnerClass(typeElement)) {
      return false;
    }

    boolean isValid = true;
    for (TypeElement type : MoreApt.getClassHierarchy(typeElement)) {
      isValid &= validateType(type);
    }

    return isValid;
  }

  public boolean hasAnyMethodsAnnotatedWithTest(TypeElement typeElement) {
    return MoreApt.getClassHierarchy(typeElement).stream()
        .flatMap(input -> ElementFilter.methodsIn(input.getEnclosedElements()).stream())
        .anyMatch(TestingPredicates.hasAnnotation(Test.class));
  }

  private final boolean validateType(TypeElement type) {
    boolean isValid = true;
    if (isAnnotationPresent(type, Ignore.class)) {
      errorReporter.report(ErrorMessage.IGNORE_ON_TYPE, type);
      isValid = false;
    }
    for (ExecutableElement executableElement : getAllTestMethods(type)) {
      isValid &= validateMemberIsPublic(executableElement);
      isValid &= validateMemberInstanceOrStatic(executableElement);
      isValid &= validateMethodNoArguments(executableElement);
      isValid &= validateMethodReturnType(executableElement);
    }
    ImmutableList variableElements = getAllTestParameters(type);
    isValid &= validateParameters(variableElements);
    return isValid;
  }

  private boolean validateParameters(ImmutableList variableElements) {
    boolean isValid = true;
    int[] usedAnnotatedIndices = new int[variableElements.size()];
    for (VariableElement each : variableElements) {
      int index = each.getAnnotation(Parameter.class).value();
      if (index < 0 || index > variableElements.size() - 1) {
        errorReporter.report(
            ErrorMessage.INVALID_PARAMETER_VALUE,
            index,
            variableElements.size(),
            variableElements.size() - 1);
        isValid = false;
      } else {
        usedAnnotatedIndices[index]++;
        isValid &= validateMemberIsPublic(each);
        isValid &= validateMemberIsInstance(each);
        isValid &= validateMemberIsNonFinal(each);
      }
    }
    for (int index = 0; index < usedAnnotatedIndices.length; index++) {
      int numberOfUse = usedAnnotatedIndices[index];
      if (numberOfUse == 0) {
        errorReporter.report(ErrorMessage.MISSING_PARAMETER, index);
        isValid = false;
      } else if (numberOfUse > 1) {
        errorReporter.report(ErrorMessage.DUPLICATE_PARAMETER, index, numberOfUse);
        isValid = false;
      }
    }
    return isValid;
  }

  private boolean validateMemberInstanceOrStatic(Element element) {
    boolean isStatic = element.getModifiers().contains(Modifier.STATIC);

    if (needsToBeStaticMethod(element) != isStatic) {
      errorReporter.report(isStatic ? ErrorMessage.IS_STATIC : ErrorMessage.NON_STATIC, element);
      return false;
    }
    return true;
  }

  private boolean needsToBeStaticMethod(Element element) {
    return hasClassSetupAnnotation(element)
        || isAnnotationPresent(element, Parameters.class)
        || isAnnotationPresent(element, BeforeParam.class)
        || isAnnotationPresent(element, AfterParam.class);
  }

  // TODO(b/235234450): clean up validation for different types of methods
  private boolean validateMethodReturnType(ExecutableElement executableElement) {
    if (isAnnotationPresent(executableElement, Parameters.class)) {
      if (!isReturnTypeIterableOrArray(executableElement)) {
        errorReporter.report(ErrorMessage.NON_ITERABLE_OR_ARRAY_RETURN, executableElement);
        return false;
      }
      return true;
    }

    boolean isValid = true;
    Test testAnnotation = executableElement.getAnnotation(Test.class);
    Timeout timeoutAnnotation = executableElement.getAnnotation(Timeout.class);
    long timeout =
        testAnnotation != null
            ? testAnnotation.timeout()
            : timeoutAnnotation != null ? timeoutAnnotation.value() : 0;

    if (testAnnotation != null && timeoutAnnotation != null) {
      errorReporter.report(ErrorMessage.TEST_HAS_TIMEOUT_ANNOTATION, executableElement);
      isValid = false;
    }

    if (TestingPredicates.IS_RETURNTYPE_A_THENABLE.test(executableElement)) {
      // if we are an async test, we need the timeout attribute
      if (timeout <= 0L) {
        errorReporter.report(ErrorMessage.ASYNC_HAS_NO_TIMEOUT, executableElement);
        isValid = false;
      }
      // block usage of expected exception with async tests
      if (MoreApt.getClassNameFromAnnotation(executableElement, Test.class, "expected")
          .isPresent()) {
        errorReporter.report(ErrorMessage.ASYNC_HAS_EXPECTED_EXCEPTION, executableElement);
        isValid = false;
      }
    } else {
      // block usage of timeout with non async tests
      if (timeout != 0L) {
        errorReporter.report(ErrorMessage.NON_ASYNC_HAS_TIMEOUT, executableElement);
        isValid = false;
      }

      if (!TestingPredicates.RETURN_TYPE_VOID_PREDICATE.test(executableElement)) {
        errorReporter.report(ErrorMessage.NON_PROMISE_RETURN, executableElement);
        isValid = false;
      }
    }
    return isValid;
  }

  private boolean isReturnTypeIterableOrArray(ExecutableElement executableElement) {

    return isArrayOfObject(executableElement)
        || isIterable(MoreApt.asTypeElement(executableElement.getReturnType()));
  }

  private final boolean isArrayOfObject(ExecutableElement executableElement) {
    TypeMirror typeMirror = executableElement.getReturnType();
    return typeMirror.getKind() == TypeKind.ARRAY
        && !MoreTypes.asArray(typeMirror).getComponentType().getKind().isPrimitive();
  }

  private final boolean isIterable(TypeElement type) {
    if (type == null) {
      return false;
    }

    return typeUtils.isSubtype(
        typeUtils.erasure(type.asType()),
        typeUtils.erasure(elementUtils.getTypeElement(Iterable.class.getName()).asType()));
  }

  private boolean validateMemberIsInstance(Element element) {
    if (element.getModifiers().contains(Modifier.STATIC)) {
      errorReporter.report(ErrorMessage.IS_STATIC, element);
      return false;
    }
    return true;
  }

  private static ImmutableList getAllTestMethods(TypeElement typeElement) {
    return ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
        .filter(
            Predicates.or(
                TestingPredicates.hasAnnotation(BeforeClass.class),
                TestingPredicates.hasAnnotation(AfterClass.class),
                TestingPredicates.hasAnnotation(Parameters.class),
                TestingPredicates.hasAnnotation(Test.class),
                TestingPredicates.hasAnnotation(Before.class),
                TestingPredicates.hasAnnotation(After.class)))
        .collect(toImmutableList());
  }

  private static boolean hasClassSetupAnnotation(Element element) {
    return isAnnotationPresent(element, BeforeClass.class)
        || isAnnotationPresent(element, AfterClass.class);
  }

  private static ImmutableList getAllTestParameters(TypeElement typeElement) {
    return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
        .filter(TestingPredicates.hasAnnotation(Parameter.class))
        .collect(toImmutableList());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy