org.junit.experimental.categories.Categories Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of junit Show documentation
Show all versions of junit Show documentation
JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
package org.junit.experimental.categories;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
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:
*
* public interface FastTests {
* }
*
* public interface SlowTests {
* }
*
* public interface SmokeTests
* }
*
* public static class A {
* @Test
* public void a() {
* fail();
* }
*
* @Category(SlowTests.class)
* @Test
* public void b() {
* }
*
* @Category({FastTests.class, SmokeTests.class})
* @Test
* public void c() {
* }
* }
*
* @Category({SlowTests.class, FastTests.class})
* public static class B {
* @Test
* public void d() {
* }
* }
*
* @RunWith(Categories.class)
* @IncludeCategory(SlowTests.class)
* @SuiteClasses({A.class, B.class})
* // Note that Categories is a kind of Suite
* public static class SlowTestSuite {
* // Will run A.b and B.d, but not A.a and A.c
* }
*
*
* Example to run multiple categories:
*
* @RunWith(Categories.class)
* @IncludeCategory({FastTests.class, SmokeTests.class})
* @SuiteClasses({A.class, B.class})
* public static class FastOrSmokeTestSuite {
* // Will run A.c and B.d, but not A.b because it is not any of FastTests or SmokeTests
* }
*
*
* @version 4.12
* @see Categories at JUnit wiki
*/
public class Categories extends Suite {
@Retention(RetentionPolicy.RUNTIME)
public @interface IncludeCategory {
/**
* Determines the tests to run that are annotated with categories specified in
* the value of this annotation or their subtypes unless excluded with {@link ExcludeCategory}.
*/
Class[] value() default {};
/**
* If true, runs tests annotated with any of the categories in
* {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with all of the categories.
*/
boolean matchAny() default true;
}
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcludeCategory {
/**
* Determines the tests which do not run if they are annotated with categories specified in the
* value of this annotation or their subtypes regardless of being included in {@link IncludeCategory#value()}.
*/
Class[] value() default {};
/**
* If true, the tests annotated with any of the categories in {@link ExcludeCategory#value()}
* do not run. Otherwise, the tests do not run if and only if annotated with all categories.
*/
boolean matchAny() default true;
}
public static class CategoryFilter extends Filter {
private final Set> included;
private final Set> excluded;
private final boolean includedAny;
private final boolean excludedAny;
public static CategoryFilter include(boolean matchAny, Class... categories) {
return new CategoryFilter(matchAny, categories, true, null);
}
public static CategoryFilter include(Class category) {
return include(true, category);
}
public static CategoryFilter include(Class... categories) {
return include(true, categories);
}
public static CategoryFilter exclude(boolean matchAny, Class... categories) {
return new CategoryFilter(true, null, matchAny, categories);
}
public static CategoryFilter exclude(Class category) {
return exclude(true, category);
}
public static CategoryFilter exclude(Class... categories) {
return exclude(true, categories);
}
public static CategoryFilter categoryFilter(boolean matchAnyInclusions, Set> inclusions,
boolean matchAnyExclusions, Set> exclusions) {
return new CategoryFilter(matchAnyInclusions, inclusions, matchAnyExclusions, exclusions);
}
@Deprecated
public CategoryFilter(Class includedCategory, Class excludedCategory) {
includedAny = true;
excludedAny = true;
included = nullableClassToSet(includedCategory);
excluded = nullableClassToSet(excludedCategory);
}
protected CategoryFilter(boolean matchAnyIncludes, Set> includes,
boolean matchAnyExcludes, Set> excludes) {
includedAny = matchAnyIncludes;
excludedAny = matchAnyExcludes;
included = copyAndRefine(includes);
excluded = copyAndRefine(excludes);
}
private CategoryFilter(boolean matchAnyIncludes, Class[] inclusions,
boolean matchAnyExcludes, Class[] exclusions) {
includedAny = matchAnyIncludes;
excludedAny = matchAnyExcludes;
included = createSet(inclusions);
excluded = createSet(exclusions);
}
/**
* @see #toString()
*/
@Override
public String describe() {
return toString();
}
/**
* Returns string in the form "[included categories] - [excluded categories]", where both
* sets have comma separated names of categories.
*
* @return string representation for the relative complement of excluded categories set
* in the set of included categories. Examples:
*
* - "categories [all]" for all included categories and no excluded ones;
*
- "categories [all] - [A, B]" for all included categories and given excluded ones;
*
- "categories [A, B] - [C, D]" for given included categories and given excluded ones.
*
* @see Class#toString() name of category
*/
@Override public String toString() {
StringBuilder description= new StringBuilder("categories ")
.append(included.isEmpty() ? "[all]" : included);
if (!excluded.isEmpty()) {
description.append(" - ").append(excluded);
}
return description.toString();
}
@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) {
final Set> childCategories= categories(description);
// If a child has no categories, immediately return.
if (childCategories.isEmpty()) {
return included.isEmpty();
}
if (!excluded.isEmpty()) {
if (excludedAny) {
if (matchesAnyParentCategories(childCategories, excluded)) {
return false;
}
} else {
if (matchesAllParentCategories(childCategories, excluded)) {
return false;
}
}
}
if (included.isEmpty()) {
// Couldn't be excluded, and with no suite's included categories treated as should run.
return true;
} else {
if (includedAny) {
return matchesAnyParentCategories(childCategories, included);
} else {
return matchesAllParentCategories(childCategories, included);
}
}
}
/**
* @return true if at least one (any) parent category match a child, otherwise false.
* If empty parentCategories, returns false.
*/
private boolean matchesAnyParentCategories(Set> childCategories, Set> parentCategories) {
for (Class parentCategory : parentCategories) {
if (hasAssignableTo(childCategories, parentCategory)) {
return true;
}
}
return false;
}
/**
* @return false if at least one parent category does not match children, otherwise true.
* If empty parentCategories, returns true.
*/
private boolean matchesAllParentCategories(Set> childCategories, Set> parentCategories) {
for (Class parentCategory : parentCategories) {
if (!hasAssignableTo(childCategories, parentCategory)) {
return false;
}
}
return true;
}
private static Set> categories(Description description) {
Set> categories= new HashSet>();
Collections.addAll(categories, directCategories(description));
Collections.addAll(categories, directCategories(parentDescription(description)));
return categories;
}
private static Description parentDescription(Description description) {
Class testClass= description.getTestClass();
return testClass == null ? null : Description.createSuiteDescription(testClass);
}
private static Class[] directCategories(Description description) {
if (description == null) {
return new Class[0];
}
Category annotation= description.getAnnotation(Category.class);
return annotation == null ? new Class[0] : annotation.value();
}
private static Set> copyAndRefine(Set> classes) {
Set> c= new LinkedHashSet>();
if (classes != null) {
c.addAll(classes);
}
c.remove(null);
return c;
}
}
public Categories(Class klass, RunnerBuilder builder) throws InitializationError {
super(klass, builder);
try {
Set> included= getIncludedCategory(klass);
Set> excluded= getExcludedCategory(klass);
boolean isAnyIncluded= isAnyIncluded(klass);
boolean isAnyExcluded= isAnyExcluded(klass);
filter(CategoryFilter.categoryFilter(isAnyIncluded, included, isAnyExcluded, excluded));
} catch (NoTestsRemainException e) {
throw new InitializationError(e);
}
}
private static Set> getIncludedCategory(Class klass) {
IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
return createSet(annotation == null ? null : annotation.value());
}
private static boolean isAnyIncluded(Class klass) {
IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
return annotation == null || annotation.matchAny();
}
private static Set> getExcludedCategory(Class klass) {
ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
return createSet(annotation == null ? null : annotation.value());
}
private static boolean isAnyExcluded(Class klass) {
ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
return annotation == null || annotation.matchAny();
}
private static boolean hasAssignableTo(Set> assigns, Class to) {
for (final Class from : assigns) {
if (to.isAssignableFrom(from)) {
return true;
}
}
return false;
}
private static Set> createSet(Class[] classes) {
// Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.12
// for include(boolean, Class...) and exclude(boolean, Class...)
if (classes == null || classes.length == 0) {
return Collections.emptySet();
}
for (Class category : classes) {
if (category == null) {
throw new NullPointerException("has null category");
}
}
return classes.length == 1
? Collections.>singleton(classes[0])
: new LinkedHashSet>(Arrays.asList(classes));
}
private static Set> nullableClassToSet(Class nullableClass) {
// Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.11
// for CategoryFilter(Class includedCategory, Class excludedCategory)
return nullableClass == null
? Collections.>emptySet()
: Collections.>singleton(nullableClass);
}
}