commit 52a87dad9e42bd84cf1fdbe284949f0108102a88 Author: Enis Soztutar Date: Mon Mar 3 17:29:46 2014 -0800 wip patch for the test diff --git hbase-client/src/test/java/org/apache/hadoop/hbase/TestAPIStability.java hbase-client/src/test/java/org/apache/hadoop/hbase/TestAPIStability.java new file mode 100644 index 0000000..a36c757 --- /dev/null +++ hbase-client/src/test/java/org/apache/hadoop/hbase/TestAPIStability.java @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.hadoop.hbase; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.hbase.ClassFinder.And; +import org.apache.hadoop.hbase.ClassFinder.FileNameFilter; +import org.apache.hadoop.hbase.ClassFinder.Not; +import org.apache.hadoop.hbase.ClassTestFinder.TestClassFilter; +import org.apache.hadoop.hbase.ClassTestFinder.TestFileNameFilter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for ensuring our client visible API's are stable. + */ +public class TestAPIStability { + + private static final Log LOG = LogFactory.getLog(TestAPIStability.class); + + /** Selects classes with generated in their package name */ + class GeneratedClassFilter implements ClassFinder.ClassFilter { + @Override + public boolean isCandidateClass(Class c) { + return c.getPackage().getName().contains("generated"); + } + } + + /** Selects classes with one of the {@link InterfaceAudience} annotation in their package name */ + class InterfaceAudienceAnnotatedClassFilter implements ClassFinder.ClassFilter { + @Override + public boolean isCandidateClass(Class c) { + if (getAnnotation(c) != null) { + // class itself has a declared annotation. + return true; + } + // If this is an internal class, look for the encapsulating class to see whether it has + // annotation. All inner classes of private classes are considered annotated. + Class enclosingClass = c.getEnclosingClass(); + if (enclosingClass == null) { + return false; // not an inner class + } + Class enclosingAnnotation = getAnnotation(enclosingClass); + if (enclosingAnnotation != null && + !InterfaceAudience.Public.class.equals(enclosingAnnotation)) { + return true; + } + return false; + } + + private Class getAnnotation(Class c) { + // we should get only declared annotations, not inherited ones + Annotation[] anns = c.getDeclaredAnnotations(); + + for (Annotation ann : anns) { + // Hadoop clearly got it wrong for not making the annotation values (private, public, ..) + // an enum instead we have three independent annotations! + Class type = ann.annotationType(); + if (type.equals(InterfaceAudience.Public.class) || + type.equals(InterfaceAudience.LimitedPrivate.class) || + type.equals(InterfaceAudience.Private.class)) { + return type; + } + } + return null; + } + } + + /** Selects classes that are declared public */ + class PublicClassFilter implements ClassFinder.ClassFilter { + @Override + public boolean isCandidateClass(Class c) { + int mod = c.getModifiers(); + return Modifier.isPublic(mod); + } + } + + /** Selects paths (jars and class dirs) only from the main code, not test classes */ + class MainCodeResourcePathFilter implements ClassFinder.ResourcePathFilter { + @Override + public boolean isCandidatePath(String resourcePath, boolean isJar) { + return !resourcePath.contains("test-classes") && + !resourcePath.contains("tests.jar"); + } + } + + /** + * Checks whether all the classes in client and common modules contain + * {@link InterfaceAudience} annotations. + */ + @Test + public void testInterfaceAudienceAnnotation() + throws ClassNotFoundException, IOException, LinkageError { + + ClassFinder classFinder = new ClassFinder( + new MainCodeResourcePathFilter(), + new Not((FileNameFilter)new TestFileNameFilter()), + + // find classes that are: + // AND are public + // NOT test classes + // AND NOT generated classes + // AND are NOT annotated with InterfaceAudience + new And(new PublicClassFilter(), + new Not(new TestClassFilter()), + new Not(new GeneratedClassFilter()), + new Not(new InterfaceAudienceAnnotatedClassFilter())) + ); + + Set> classes = classFinder.findClasses(false); + + LOG.info("These are the classes that DO NOT have @InterfaceAudience annotation:"); + for (Class clazz : classes) { + LOG.info(clazz); + } + + Assert.assertEquals("All classes should have @InterfaceAudience annotation", + 0, classes.size()); + } + + // TODO all InterfaceAudience.Public interfaces should also have Stability as well. + +} diff --git hbase-common/src/test/java/org/apache/hadoop/hbase/ClassFinder.java hbase-common/src/test/java/org/apache/hadoop/hbase/ClassFinder.java index 08799ba..acc78f6 100644 --- hbase-common/src/test/java/org/apache/hadoop/hbase/ClassFinder.java +++ hbase-common/src/test/java/org/apache/hadoop/hbase/ClassFinder.java @@ -62,6 +62,43 @@ public class ClassFinder { boolean isCandidateClass(Class c); }; + public static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter { + private ResourcePathFilter resourcePathFilter; + private FileNameFilter fileNameFilter; + private ClassFilter classFilter; + + public Not(ResourcePathFilter resourcePathFilter){this.resourcePathFilter = resourcePathFilter;} + public Not(FileNameFilter fileNameFilter){this.fileNameFilter = fileNameFilter;} + public Not(ClassFilter classFilter){this.classFilter = classFilter;} + + @Override + public boolean isCandidatePath(String resourcePath, boolean isJar) { + return !resourcePathFilter.isCandidatePath(resourcePath, isJar); + } + @Override + public boolean isCandidateFile(String fileName, String absFilePath) { + return !fileNameFilter.isCandidateFile(fileName, absFilePath); + } + @Override + public boolean isCandidateClass(Class c) { + return !classFilter.isCandidateClass(c); + } + } + + public static class And implements ClassFilter { + ClassFilter[] classFilters; + public And(ClassFilter...classFilters) { this.classFilters = classFilters; } + @Override + public boolean isCandidateClass(Class c) { + for (ClassFilter filter : classFilters) { + if (!filter.isCandidateClass(c)) { + return false; + } + } + return true; + } + } + public ClassFinder() { this(null, null, null); }