001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.regex.Pattern;
025import org.junit.experimental.categories.Category;
026import org.junit.jupiter.api.Tag;
027import org.junit.runners.Suite;
028
029/**
030 * ClassFinder that is pre-configured with filters that will only allow test classes. The name is
031 * strange because a logical name would start with "Test" and be confusing.
032 */
033public class ClassTestFinder extends ClassFinder {
034
035  public ClassTestFinder() {
036    super(new TestFileNameFilter(), new TestFileNameFilter(), new TestClassFilter());
037  }
038
039  public ClassTestFinder(Class<?> category) {
040    super(new TestFileNameFilter(), new TestFileNameFilter(), new TestClassFilter(category));
041  }
042
043  public static Class<?>[] getCategoryAnnotations(Class<?> c) {
044    Category category = c.getAnnotation(Category.class);
045    if (category != null) {
046      return category.value();
047    }
048    return new Class<?>[0];
049  }
050
051  public static String[] getTagAnnotations(Class<?> c) {
052    // TODO handle optional Tags annotation
053    Tag[] tags = c.getAnnotationsByType(Tag.class);
054    List<String> values = new ArrayList<>();
055    for (Tag tag : tags) {
056      values.add(tag.value());
057    }
058    return values.toArray(new String[values.size()]);
059  }
060
061  /** Filters both test classes and anything in the hadoop-compat modules */
062  public static class TestFileNameFilter implements FileNameFilter, ResourcePathFilter {
063    private static final Pattern hadoopCompactRe = Pattern.compile("hbase-hadoop\\d?-compat");
064
065    @Override
066    public boolean isCandidateFile(String fileName, String absFilePath) {
067      boolean isTestFile = fileName.startsWith("Test") || fileName.startsWith("IntegrationTest");
068      return isTestFile && !hadoopCompactRe.matcher(absFilePath).find();
069    }
070
071    @Override
072    public boolean isCandidatePath(String resourcePath, boolean isJar) {
073      return !hadoopCompactRe.matcher(resourcePath).find();
074    }
075  }
076
077  /*
078   * A class is considered as a test class if: - it's not Abstract AND - one or more of its methods
079   * is annotated with org.junit.Test OR - the class is annotated with Suite.SuiteClasses
080   */
081  public static class TestClassFilter implements ClassFilter {
082    private Class<?> categoryAnnotation = null;
083
084    public TestClassFilter(Class<?> categoryAnnotation) {
085      this.categoryAnnotation = categoryAnnotation;
086    }
087
088    public TestClassFilter() {
089      this(null);
090    }
091
092    @Override
093    public boolean isCandidateClass(Class<?> c) {
094      return isTestClass(c) && isCategorizedClass(c);
095    }
096
097    private boolean isTestClass(Class<?> c) {
098      if (Modifier.isAbstract(c.getModifiers())) {
099        return false;
100      }
101
102      if (c.getAnnotation(Suite.SuiteClasses.class) != null) {
103        return true;
104      }
105
106      for (Method met : c.getMethods()) {
107        if (
108          met.getAnnotation(org.junit.Test.class) != null
109            || met.getAnnotation(org.junit.jupiter.api.Test.class) != null
110        ) {
111          return true;
112        }
113      }
114
115      return false;
116    }
117
118    private boolean isCategorizedClass(Class<?> c) {
119      if (this.categoryAnnotation == null) {
120        return true;
121      }
122      for (Class<?> cc : getCategoryAnnotations(c)) {
123        if (cc.equals(this.categoryAnnotation)) {
124          return true;
125        }
126      }
127      return false;
128    }
129  }
130}