diff options
Diffstat (limited to 'src/main/java/org/junit/experimental/categories/Categories.java')
-rw-r--r-- | src/main/java/org/junit/experimental/categories/Categories.java | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java new file mode 100644 index 0000000..d57b4d3 --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/Categories.java @@ -0,0 +1,192 @@ +/** + * + */ +package org.junit.experimental.categories; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +/** + * From a given set of test classes, runs only the classes and methods that are + * annotated with either the category given with the @IncludeCategory + * annotation, or a subtype of that category. + * + * Note that, for now, annotating suites with {@code @Category} has no effect. + * Categories must be annotated on the direct method or class. + * + * Example: + * + * <pre> + * public interface FastTests { + * } + * + * public interface SlowTests { + * } + * + * public static class A { + * @Test + * public void a() { + * fail(); + * } + * + * @Category(SlowTests.class) + * @Test + * public void b() { + * } + * } + * + * @Category( { SlowTests.class, FastTests.class }) + * public static class B { + * @Test + * public void c() { + * + * } + * } + * + * @RunWith(Categories.class) + * @IncludeCategory(SlowTests.class) + * @SuiteClasses( { A.class, B.class }) + * // Note that Categories is a kind of Suite + * public static class SlowTestSuite { + * } + * </pre> + */ +public class Categories extends Suite { + // the way filters are implemented makes this unnecessarily complicated, + // buggy, and difficult to specify. A new way of handling filters could + // someday enable a better new implementation. + // https://github.com/KentBeck/junit/issues/issue/172 + + @Retention(RetentionPolicy.RUNTIME) + public @interface IncludeCategory { + public Class<?> value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface ExcludeCategory { + public Class<?> value(); + } + + public static class CategoryFilter extends Filter { + public static CategoryFilter include(Class<?> categoryType) { + return new CategoryFilter(categoryType, null); + } + + private final Class<?> fIncluded; + + private final Class<?> fExcluded; + + public CategoryFilter(Class<?> includedCategory, + Class<?> excludedCategory) { + fIncluded= includedCategory; + fExcluded= excludedCategory; + } + + @Override + public String describe() { + return "category " + fIncluded; + } + + @Override + public boolean shouldRun(Description description) { + if (hasCorrectCategoryAnnotation(description)) + return true; + for (Description each : description.getChildren()) + if (shouldRun(each)) + return true; + return false; + } + + private boolean hasCorrectCategoryAnnotation(Description description) { + List<Class<?>> categories= categories(description); + if (categories.isEmpty()) + return fIncluded == null; + for (Class<?> each : categories) + if (fExcluded != null && fExcluded.isAssignableFrom(each)) + return false; + for (Class<?> each : categories) + if (fIncluded == null || fIncluded.isAssignableFrom(each)) + return true; + return false; + } + + private List<Class<?>> categories(Description description) { + ArrayList<Class<?>> categories= new ArrayList<Class<?>>(); + categories.addAll(Arrays.asList(directCategories(description))); + categories.addAll(Arrays.asList(directCategories(parentDescription(description)))); + return categories; + } + + private Description parentDescription(Description description) { + Class<?> testClass= description.getTestClass(); + if (testClass == null) + return null; + return Description.createSuiteDescription(testClass); + } + + private Class<?>[] directCategories(Description description) { + if (description == null) + return new Class<?>[0]; + Category annotation= description.getAnnotation(Category.class); + if (annotation == null) + return new Class<?>[0]; + return annotation.value(); + } + } + + public Categories(Class<?> klass, RunnerBuilder builder) + throws InitializationError { + super(klass, builder); + try { + filter(new CategoryFilter(getIncludedCategory(klass), + getExcludedCategory(klass))); + } catch (NoTestsRemainException e) { + throw new InitializationError(e); + } + assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); + } + + private Class<?> getIncludedCategory(Class<?> klass) { + IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class); + return annotation == null ? null : annotation.value(); + } + + private Class<?> getExcludedCategory(Class<?> klass) { + ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class); + return annotation == null ? null : annotation.value(); + } + + private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError { + if (!canHaveCategorizedChildren(description)) + assertNoDescendantsHaveCategoryAnnotations(description); + for (Description each : description.getChildren()) + assertNoCategorizedDescendentsOfUncategorizeableParents(each); + } + + private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError { + for (Description each : description.getChildren()) { + if (each.getAnnotation(Category.class) != null) + throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods."); + assertNoDescendantsHaveCategoryAnnotations(each); + } + } + + // If children have names like [0], our current magical category code can't determine their + // parentage. + private static boolean canHaveCategorizedChildren(Description description) { + for (Description each : description.getChildren()) + if (each.getTestClass() == null) + return false; + return true; + } +}
\ No newline at end of file |