diff --git a/src/main/java/org/junit/experimental/categories/Category.java b/src/main/java/org/junit/experimental/categories/Category.java index 014405165f72..8eae83677c94 100644 --- a/src/main/java/org/junit/experimental/categories/Category.java +++ b/src/main/java/org/junit/experimental/categories/Category.java @@ -4,6 +4,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import org.junit.validator.ValidateWith; + /** * Marks a test class or test method as belonging to one or more categories of tests. * The value is an array of arbitrary classes. @@ -40,6 +42,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Inherited +@ValidateWith(CategoryValidator.class) public @interface Category { Class[] value(); } \ No newline at end of file diff --git a/src/main/java/org/junit/experimental/categories/CategoryValidator.java b/src/main/java/org/junit/experimental/categories/CategoryValidator.java new file mode 100644 index 000000000000..8ef12bfb637d --- /dev/null +++ b/src/main/java/org/junit/experimental/categories/CategoryValidator.java @@ -0,0 +1,61 @@ +package org.junit.experimental.categories; + +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; +import static java.util.Arrays.asList; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runners.model.FrameworkMethod; +import org.junit.validator.AnnotationValidator; + +/** + * Validates that there are no errors in the use of the {@code Category} + * annotation. If there is, a {@code Throwable} object will be added to the list + * of errors. + * + * @since 4.12 + */ +public final class CategoryValidator extends AnnotationValidator { + + private static final Set> INCOMPATIBLE_ANNOTATIONS = unmodifiableSet(new HashSet>( + asList(BeforeClass.class, AfterClass.class, Before.class, After.class))); + + /** + * Adds to {@code errors} a throwable for each problem detected. Looks for + * {@code BeforeClass}, {@code AfterClass}, {@code Before} and {@code After} + * annotations. + * + * @param method the method that is being validated + * @return A list of exceptions detected + * + * @since 4.12 + */ + @Override + public List validateAnnotatedMethod(FrameworkMethod method) { + List errors = new ArrayList(); + Annotation[] annotations = method.getAnnotations(); + for (Annotation annotation : annotations) { + for (Class clazz : INCOMPATIBLE_ANNOTATIONS) { + if (annotation.annotationType().isAssignableFrom(clazz)) { + addErrorMessage(errors, clazz); + } + } + } + return unmodifiableList(errors); + } + + private void addErrorMessage(List errors, Class clazz) { + String message = String.format("@%s can not be combined with @Category", + clazz.getSimpleName()); + errors.add(new Exception(message)); + } +} diff --git a/src/main/java/org/junit/runners/ParentRunner.java b/src/main/java/org/junit/runners/ParentRunner.java index 0384a9cd7bed..9e4495bb0a1b 100644 --- a/src/main/java/org/junit/runners/ParentRunner.java +++ b/src/main/java/org/junit/runners/ParentRunner.java @@ -11,11 +11,15 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; +import org.junit.validator.AnnotationValidator; +import org.junit.validator.AnnotationValidatorFactory; +import org.junit.validator.ValidateWith; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.internal.runners.statements.RunAfters; @@ -31,9 +35,9 @@ import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; -import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; @@ -59,6 +63,9 @@ public abstract class ParentRunner extends Runner implements Filterable, // Guarded by fChildrenLock private volatile Collection fFilteredChildren = null; + private final AnnotationValidatorFactory fAnnotationValidatorFactory = + new AnnotationValidatorFactory(); + private volatile RunnerScheduler fScheduler = new RunnerScheduler() { public void schedule(Runnable childStatement) { childStatement.run(); @@ -114,6 +121,54 @@ protected void collectInitializationErrors(List errors) { validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); validatePublicVoidNoArgMethods(AfterClass.class, true, errors); validateClassRules(errors); + invokeValidators(errors); + } + + private void invokeValidators(List errors) { + invokeValidatorsOnClass(errors); + invokeValidatorsOnMethods(errors); + invokeValidatorsOnFields(errors); + } + + private void invokeValidatorsOnClass(List errors) { + Annotation[] annotations = getTestClass().getAnnotations(); + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + ValidateWith validateWithAnnotation = annotationType.getAnnotation(ValidateWith.class); + if (validateWithAnnotation != null) { + AnnotationValidator annotationValidator = + fAnnotationValidatorFactory.createAnnotationValidator(validateWithAnnotation); + errors.addAll(annotationValidator.validateAnnotatedClass(getTestClass())); + } + } + } + + private void invokeValidatorsOnMethods(List errors) { + Map, List> annotationMap = getTestClass().getAnnotationToMethods(); + for (Class annotationType : annotationMap.keySet()) { + ValidateWith validateWithAnnotation = annotationType.getAnnotation(ValidateWith.class); + if (validateWithAnnotation != null) { + for (FrameworkMethod frameworkMethod : annotationMap.get(annotationType)) { + AnnotationValidator annotationValidator = + fAnnotationValidatorFactory.createAnnotationValidator(validateWithAnnotation); + errors.addAll(annotationValidator.validateAnnotatedMethod(frameworkMethod)); + } + } + } + } + + private void invokeValidatorsOnFields(List errors) { + Map, List> annotationMap = getTestClass().getAnnotationToFields(); + for (Class annotationType : annotationMap.keySet()) { + ValidateWith validateWithAnnotation = annotationType.getAnnotation(ValidateWith.class); + if (validateWithAnnotation != null) { + for (FrameworkField frameworkField : annotationMap.get(annotationType)) { + AnnotationValidator annotationValidator = + fAnnotationValidatorFactory.createAnnotationValidator(validateWithAnnotation); + errors.addAll(annotationValidator.validateAnnotatedField(frameworkField)); + } + } + } } /** diff --git a/src/main/java/org/junit/runners/model/TestClass.java b/src/main/java/org/junit/runners/model/TestClass.java index 18b274441a93..b7a0f7510d1d 100644 --- a/src/main/java/org/junit/runners/model/TestClass.java +++ b/src/main/java/org/junit/runners/model/TestClass.java @@ -7,8 +7,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -24,8 +26,8 @@ */ public class TestClass { private final Class fClass; - private final Map, List> fMethodsForAnnotations; - private final Map, List> fFieldsForAnnotations; + private final Map, List> fMethodsForAnnotations; + private final Map, List> fFieldsForAnnotations; /** * Creates a {@code TestClass} wrapping {@code klass}. Each time this @@ -40,22 +42,38 @@ public TestClass(Class klass) { "Test class can only have one constructor"); } - Map, List> methodsForAnnotations = new HashMap, List>(); - Map, List> fieldsForAnnotations = new HashMap, List>(); + Map, List> methodsForAnnotations = + new LinkedHashMap, List>(); + Map, List> fieldsForAnnotations = + new LinkedHashMap, List>(); + for (Class eachClass : getSuperClasses(fClass)) { for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) { addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations); } - for (Field eachField : eachClass.getDeclaredFields()) { + // ensuring fields are sorted to make sure that entries are inserted + // and read from fieldForAnnotations in a deterministic order + for (Field eachField : getSortedDeclaredFields(eachClass)) { addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations); } } - fMethodsForAnnotations = Collections.unmodifiableMap(methodsForAnnotations); - fFieldsForAnnotations = Collections.unmodifiableMap(fieldsForAnnotations); + + fMethodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations); + fFieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations); + } + + private static Field[] getSortedDeclaredFields(Class clazz) { + Field[] declaredFields = clazz.getDeclaredFields(); + Arrays.sort(declaredFields, new Comparator() { + public int compare(Field field1, Field field2) { + return field1.getName().compareTo(field2.getName()); + } + }); + return declaredFields; } private static > void addToAnnotationLists(T member, - Map, List> map) { + Map, List> map) { for (Annotation each : member.getAnnotations()) { Class type = each.annotationType(); List members = getAnnotatedMembers(map, type, true); @@ -70,6 +88,17 @@ private static > void addToAnnotationLists(T member } } + private static > Map, List> + makeDeeplyUnmodifiable(Map, List> source) { + LinkedHashMap, List> copy = + new LinkedHashMap, List>(); + for (Map.Entry, List> entry : source.entrySet()) { + copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); + } + return Collections.unmodifiableMap(copy); + } + + /** * Returns, efficiently, all the non-overridden methods in this class and * its superclasses that are annotated with {@code annotationClass}. @@ -88,7 +117,27 @@ public List getAnnotatedFields( return Collections.unmodifiableList(getAnnotatedMembers(fFieldsForAnnotations, annotationClass, false)); } - private static List getAnnotatedMembers(Map, List> map, + /** + * Gets a {@code Map} between annotations and methods that have + * the annotation in this class or its superclasses. + * + * @since 4.12 + */ + public Map, List> getAnnotationToMethods() { + return fMethodsForAnnotations; + } + + /** + * Gets a {@code Map} between annotations and fields that have + * the annotation in this class or its superclasses. + * + * @since 4.12 + */ + public Map, List> getAnnotationToFields() { + return fFieldsForAnnotations; + } + + private static List getAnnotatedMembers(Map, List> map, Class type, boolean fillIfAbsent) { if (!map.containsKey(type) && fillIfAbsent) { map.put(type, new ArrayList()); diff --git a/src/main/java/org/junit/validator/AnnotationValidator.java b/src/main/java/org/junit/validator/AnnotationValidator.java new file mode 100644 index 000000000000..8a53adfd54e6 --- /dev/null +++ b/src/main/java/org/junit/validator/AnnotationValidator.java @@ -0,0 +1,60 @@ +package org.junit.validator; + +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +import static java.util.Collections.emptyList; + +import java.util.List; + +/** + * Validates annotations on classes and methods. To be validated, + * an annotation should be annotated with {@link ValidateWith} + * + * Instances of this class are shared by multiple test runners, so they should + * be immutable and thread-safe. + * + * @since 4.12 + */ +public abstract class AnnotationValidator { + + private static final List NO_VALIDATION_ERRORS = emptyList(); + + /** + * Validates annotation on the given class. + * + * @param testClass that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List validateAnnotatedClass(TestClass testClass) { + return NO_VALIDATION_ERRORS; + } + + /** + * Validates annotation on the given field. + * + * @param field that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List validateAnnotatedField(FrameworkField field) { + return NO_VALIDATION_ERRORS; + + } + + /** + * Validates annotation on the given method. + * + * @param method that is being validated + * @return A list of exceptions. Default behavior is to return an empty list. + * + * @since 4.12 + */ + public List validateAnnotatedMethod(FrameworkMethod method) { + return NO_VALIDATION_ERRORS; + } +} diff --git a/src/main/java/org/junit/validator/AnnotationValidatorFactory.java b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java new file mode 100644 index 000000000000..d0157b91ed5c --- /dev/null +++ b/src/main/java/org/junit/validator/AnnotationValidatorFactory.java @@ -0,0 +1,44 @@ +package org.junit.validator; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Creates instances of Annotation Validators. + * + * @since 4.12 + */ +public class AnnotationValidatorFactory { + + private static ConcurrentHashMap fAnnotationTypeToValidatorMap = + new ConcurrentHashMap(); + + /** + * Creates the AnnotationValidator specified by the value in + * {@link org.junit.validator.ValidateWith}. Instances are + * cached. + * + * @param validateWithAnnotation + * @return An instance of the AnnotationValidator. + * + * @since 4.12 + */ + public AnnotationValidator createAnnotationValidator(ValidateWith validateWithAnnotation) { + AnnotationValidator validator = fAnnotationTypeToValidatorMap.get(validateWithAnnotation); + if (validator != null) { + return validator; + } + + Class clazz = validateWithAnnotation.value(); + if (clazz == null) { + throw new IllegalArgumentException("Can't create validator, value is null in annotation " + validateWithAnnotation.getClass().getName()); + } + try { + AnnotationValidator annotationValidator = clazz.newInstance(); + fAnnotationTypeToValidatorMap.putIfAbsent(validateWithAnnotation, annotationValidator); + return fAnnotationTypeToValidatorMap.get(validateWithAnnotation); + } catch (Exception e) { + throw new RuntimeException("Exception received when creating AnnotationValidator class " + clazz.getName(), e); + } + } + +} diff --git a/src/main/java/org/junit/validator/ValidateWith.java b/src/main/java/org/junit/validator/ValidateWith.java new file mode 100644 index 000000000000..03d790670f75 --- /dev/null +++ b/src/main/java/org/junit/validator/ValidateWith.java @@ -0,0 +1,19 @@ +package org.junit.validator; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Allows for an {@link AnnotationValidator} to be attached to an annotation. + * + *

When attached to an annotation, the validator will be instantiated and invoked + * by the {@link org.junit.runners.ParentRunner}.

+ * + * @since 4.12 + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ValidateWith { + Class value(); +} diff --git a/src/test/java/org/junit/tests/experimental/categories/CategoryValidatorTest.java b/src/test/java/org/junit/tests/experimental/categories/CategoryValidatorTest.java new file mode 100644 index 000000000000..8f7397ee9d91 --- /dev/null +++ b/src/test/java/org/junit/tests/experimental/categories/CategoryValidatorTest.java @@ -0,0 +1,88 @@ +package org.junit.tests.experimental.categories; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.experimental.categories.CategoryValidator; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +public class CategoryValidatorTest { + + public static class SampleCategory { + } + + public static class CategoryTest { + @BeforeClass + @Category(value = SampleCategory.class) + public static void methodWithCategoryAndBeforeClass() { + } + + @AfterClass + @Category(value = SampleCategory.class) + public static void methodWithCategoryAndAfterClass() { + } + + @Before + @Category(value = SampleCategory.class) + public static void methodWithCategoryAndBefore() { + } + + @After + @Category(value = SampleCategory.class) + public static void methodWithCategoryAndAfter() { + } + + @Category(value = SampleCategory.class) + public static void methodWithCategory() { + } + } + + @Test + public void errorIsAddedWhenCategoryIsUsedWithBeforeClass() { + FrameworkMethod method = new TestClass(CategoryTest.class).getAnnotatedMethods(BeforeClass.class).get(0); + testAndAssertErrorMessage(method, "@BeforeClass can not be combined with @Category"); + } + + @Test + public void errorIsAddedWhenCategoryIsUsedWithAfterClass() { + FrameworkMethod method = new TestClass(CategoryTest.class).getAnnotatedMethods(AfterClass.class).get(0); + testAndAssertErrorMessage(method, "@AfterClass can not be combined with @Category"); + } + + @Test + public void errorIsAddedWhenCategoryIsUsedWithBefore() { + FrameworkMethod method = new TestClass(CategoryTest.class).getAnnotatedMethods(Before.class).get(0); + testAndAssertErrorMessage(method, "@Before can not be combined with @Category"); + } + + @Test + public void errorIsAddedWhenCategoryIsUsedWithAfter() { + FrameworkMethod method = new TestClass(CategoryTest.class).getAnnotatedMethods(After.class).get(0); + testAndAssertErrorMessage(method, "@After can not be combined with @Category"); + } + + private void testAndAssertErrorMessage(FrameworkMethod method, String expectedErrorMessage) { + List errors = new CategoryValidator().validateAnnotatedMethod(method); + + assertThat(errors.size(), is(1)); + Exception exception = errors.get(0); + assertThat(exception.getMessage(), is(expectedErrorMessage)); + } + + @Test + public void errorIsNotAddedWhenCategoryIsNotCombinedWithIllegalCombination() throws NoSuchMethodException { + FrameworkMethod method = new FrameworkMethod(CategoryTest.class.getMethod("methodWithCategory")); + List errors = new CategoryValidator().validateAnnotatedMethod(method); + + assertThat(errors.size(), is(0)); + } +} diff --git a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java index e02430d11ee1..9cf3d4f42732 100644 --- a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java +++ b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java @@ -1,20 +1,19 @@ package org.junit.tests.running.classes; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.util.List; - import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Test; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; +import org.junit.validator.AnnotationValidator; +import org.junit.validator.ValidateWith; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Request; import org.junit.runner.Result; import org.junit.runner.manipulation.Filter; +import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.ParentRunner; @@ -23,6 +22,17 @@ import org.junit.tests.experimental.rules.RuleFieldValidatorTest.TestWithNonStaticClassRule; import org.junit.tests.experimental.rules.RuleFieldValidatorTest.TestWithProtectedClassRule; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.hamcrest.core.IsCollectionContaining.hasItem; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + public class ParentRunnerTest { public static String log = ""; @@ -131,9 +141,79 @@ private void assertClassHasFailureMessage(Class klass, String message) { JUnitCore junitCore = new JUnitCore(); Request request = Request.aClass(klass); Result result = junitCore.run(request); - assertThat(result.getFailureCount(), is(2)); //the second failure is no runnable methods - assertThat(result.getFailures().get(0).getMessage(), - is(equalTo(message))); + List messages = new ArrayList(); + for (Failure failure : result.getFailures()) { + messages.add(failure.getMessage()); + } + assertThat(messages, hasItem(message)); + + } + + public static class ExampleAnnotationValidator extends AnnotationValidator { + private static final String ANNOTATED_METHOD_CALLED = "annotated method called"; + private static final String ANNOTATED_FIELD_CALLED = "annotated field called"; + private static final String ANNOTATED_CLASS_CALLED = "annotated class called"; + + @Override + public List validateAnnotatedClass(TestClass testClass) { + return asList(new Exception(ANNOTATED_CLASS_CALLED)); + } + + @Override + public List validateAnnotatedField(FrameworkField field) { + return asList(new Exception(ANNOTATED_FIELD_CALLED)); + } + + @Override + public List validateAnnotatedMethod(FrameworkMethod method) { + return asList(new Exception(ANNOTATED_METHOD_CALLED)); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @ValidateWith(ExampleAnnotationValidator.class) + public @interface ExampleAnnotationWithValidator { + } + + public static class AnnotationValidatorMethodTest { + @ExampleAnnotationWithValidator + @Test + public void test() { + } + } + + public static class AnnotationValidatorFieldTest { + @ExampleAnnotationWithValidator + private String field; + + @Test + public void test() { + } + } + @ExampleAnnotationWithValidator + public static class AnnotationValidatorClassTest { + @Test + public void test() { + } + } + + @Test + public void validatorIsCalledForAClass() { + assertClassHasFailureMessage(AnnotationValidatorClassTest.class, + ExampleAnnotationValidator.ANNOTATED_CLASS_CALLED); + } + + @Test + public void validatorIsCalledForAMethod() throws InitializationError { + assertClassHasFailureMessage(AnnotationValidatorMethodTest.class, + ExampleAnnotationValidator.ANNOTATED_METHOD_CALLED); + } + + @Test + public void validatorIsCalledForAField() { + assertClassHasFailureMessage(AnnotationValidatorFieldTest.class, + ExampleAnnotationValidator.ANNOTATED_FIELD_CALLED); } } diff --git a/src/test/java/org/junit/tests/running/classes/TestClassTest.java b/src/test/java/org/junit/tests/running/classes/TestClassTest.java index 7675a9cbdf2c..7a6dedd86f06 100644 --- a/src/test/java/org/junit/tests/running/classes/TestClassTest.java +++ b/src/test/java/org/junit/tests/running/classes/TestClassTest.java @@ -4,15 +4,29 @@ import static org.hamcrest.CoreMatchers.hasItem; import static org.junit.Assert.assertThat; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import org.hamcrest.CoreMatchers; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.theories.DataPoint; +import org.junit.rules.ExpectedException; import org.junit.rules.TestRule; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.TestClass; public class TestClassTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + public static class TwoConstructors { public TwoConstructors() { } @@ -114,4 +128,80 @@ public void annotatedMethodValues() { assertThat(values, hasItem("jupiter")); assertThat(values.size(), is(1)); } + + @Test + public void annotationToMethods() { + TestClass tc = new TestClass(MethodsAnnotated.class); + Map, List> annotationToMethods = tc.getAnnotationToMethods(); + List methods = annotationToMethods.get(Ignore.class); + assertThat(methods.size(), is(2)); + } + + @Test + public void annotationToMethodsReturnsUnmodifiableMap() { + TestClass tc = new TestClass(MethodsAnnotated.class); + Map, List> annotationToMethods = tc.getAnnotationToMethods(); + exception.expect(UnsupportedOperationException.class); + annotationToMethods.put(Ignore.class, null); + } + + @Test + public void annotationToMethodsReturnsValuesInTheMapThatAreUnmodifiable() { + TestClass tc = new TestClass(MethodsAnnotated.class); + Map, List> annotationToMethods = tc.getAnnotationToMethods(); + List methods = annotationToMethods.get(Ignore.class); + exception.expect(UnsupportedOperationException.class); + methods.add(null); + } + + @Test + public void annotationToFields() { + TestClass tc = new TestClass(FieldAnnotated.class); + Map, List> annotationToFields = tc.getAnnotationToFields(); + List fields = annotationToFields.get(Rule.class); + assertThat(fields.size(), is(2)); + } + + @Test + public void annotationToFieldsReturnsUnmodifiableMap() { + TestClass tc = new TestClass(FieldAnnotated.class); + Map, List> annotationToFields = tc.getAnnotationToFields(); + exception.expect(UnsupportedOperationException.class); + annotationToFields.put(Rule.class, null); + } + + @Test + public void annotationToFieldsReturnsValuesInTheMapThatAreUnmodifiable() { + TestClass tc = new TestClass(FieldAnnotated.class); + Map, List> annotationToFields = tc.getAnnotationToFields(); + List fields = annotationToFields.get(Rule.class); + exception.expect(UnsupportedOperationException.class); + fields.add(null); + } + + public static class MultipleFieldsAnnotated { + @DataPoint + public String a = "testing a"; + + @Rule + public boolean b; + + @DataPoint + public String c = "testing c"; + + @Rule + public boolean d; + } + + @Test + public void annotationToFieldsReturnsKeysInADeterministicOrder() { + TestClass tc = new TestClass(MultipleFieldsAnnotated.class); + Map, List> annotationToFields = tc.getAnnotationToFields(); + List> keys = new ArrayList>(); + for (Class annotation : annotationToFields.keySet()) { + keys.add(annotation); + } + assertThat(keys.get(0), CoreMatchers.>is(DataPoint.class)); + assertThat(keys.get(1), CoreMatchers.>is(Rule.class)); + } } diff --git a/src/test/java/org/junit/validator/AnnotationValidatorFactoryTest.java b/src/test/java/org/junit/validator/AnnotationValidatorFactoryTest.java new file mode 100644 index 000000000000..edab6277c187 --- /dev/null +++ b/src/test/java/org/junit/validator/AnnotationValidatorFactoryTest.java @@ -0,0 +1,70 @@ +package org.junit.validator; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.lang.annotation.Annotation; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertThat; + +public class AnnotationValidatorFactoryTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void createAnnotationValidator() { + ValidateWith validateWith = SampleTestWithValidator.class.getAnnotation(ValidateWith.class); + AnnotationValidator annotationValidator = new AnnotationValidatorFactory().createAnnotationValidator(validateWith); + assertThat(annotationValidator, is(instanceOf(Validator.class))); + } + + @Test + public void exceptionWhenAnnotationWithNullClassIsPassedIn() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Can't create validator, value is null in " + + "annotation org.junit.validator.AnnotationValidatorFactoryTest$ValidatorWithNullValue"); + + new AnnotationValidatorFactory().createAnnotationValidator(new ValidatorWithNullValue()); + } + + + public static class ValidatorWithNullValue implements ValidateWith { + public Class value() { + return null; + } + + public Class annotationType() { + return ValidateWith.class; + } + } + + @ValidateWith(value = Validator.class) + public static class SampleTestWithValidator { + } + + public static class Validator extends AnnotationValidator { + } + + @Test + public void exceptionWhenAnnotationValidatorCantBeCreated() { + ValidateWith validateWith = SampleTestWithValidatorThatThrowsException.class.getAnnotation(ValidateWith.class); + exception.expect(RuntimeException.class); + exception.expectMessage("Exception received when creating AnnotationValidator class " + + "org.junit.validator.AnnotationValidatorFactoryTest$ValidatorThatThrowsException"); + new AnnotationValidatorFactory().createAnnotationValidator(validateWith); + } + + @ValidateWith(value = ValidatorThatThrowsException.class) + public static class SampleTestWithValidatorThatThrowsException { + } + + public static class ValidatorThatThrowsException extends AnnotationValidator { + public ValidatorThatThrowsException() throws InstantiationException { + throw new InstantiationException("Simulating exception in test"); + } + } +}