aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java')
-rw-r--r--src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java344
1 files changed, 266 insertions, 78 deletions
diff --git a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
index 23f921b..36de4f1 100644
--- a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
+++ b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java
@@ -1,91 +1,279 @@
package org.junit.internal.runners.rules;
-import java.lang.annotation.Annotation;
-import java.util.List;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.rules.MethodRule;
import org.junit.rules.TestRule;
-import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMember;
import org.junit.runners.model.TestClass;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
/**
- * A RuleFieldValidator validates the rule fields of a
- * {@link TestClass}. All reasons for rejecting the
+ * A RuleMemberValidator validates the rule fields/methods of a
+ * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the
* {@code TestClass} are written to a list of errors.
*
- * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
+ * <p>There are four slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
* validates fields with a {@link ClassRule} annotation and the
- * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.
+ * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.</p>
+ *
+ * <p>The {@link #CLASS_RULE_METHOD_VALIDATOR}
+ * validates methods with a {@link ClassRule} annotation and the
+ * {@link #RULE_METHOD_VALIDATOR} validates methods with a {@link Rule} annotation.</p>
*/
-public enum RuleMemberValidator {
- /**
- * Validates fields with a {@link ClassRule} annotation.
- */
- CLASS_RULE_VALIDATOR(ClassRule.class, true),
- /**
- * Validates fields with a {@link Rule} annotation.
- */
- RULE_VALIDATOR(Rule.class, false);
-
- private final Class<? extends Annotation> fAnnotation;
-
- private final boolean fOnlyStaticFields;
-
- private RuleMemberValidator(Class<? extends Annotation> annotation,
- boolean onlyStaticFields) {
- this.fAnnotation= annotation;
- this.fOnlyStaticFields= onlyStaticFields;
- }
-
- /**
- * Validate the {@link TestClass} and adds reasons
- * for rejecting the class to a list of errors.
- * @param target the {@code TestClass} to validate.
- * @param errors the list of errors.
- */
- public void validate(TestClass target, List<Throwable> errors) {
- List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation);
- for (FrameworkField each : fields)
- validateField(each, errors);
- }
-
- private void validateField(FrameworkField field, List<Throwable> errors) {
- optionallyValidateStatic(field, errors);
- validatePublic(field, errors);
- validateTestRuleOrMethodRule(field, errors);
- }
-
- private void optionallyValidateStatic(FrameworkField field,
- List<Throwable> errors) {
- if (fOnlyStaticFields && !field.isStatic())
- addError(errors, field, "must be static.");
- }
-
- private void validatePublic(FrameworkField field, List<Throwable> errors) {
- if (!field.isPublic())
- addError(errors, field, "must be public.");
- }
-
- private void validateTestRuleOrMethodRule(FrameworkField field,
- List<Throwable> errors) {
- if (!isMethodRule(field) && !isTestRule(field))
- addError(errors, field, "must implement MethodRule or TestRule.");
- }
-
- private boolean isTestRule(FrameworkField target) {
- return TestRule.class.isAssignableFrom(target.getType());
- }
-
- @SuppressWarnings("deprecation")
- private boolean isMethodRule(FrameworkField target) {
- return org.junit.rules.MethodRule.class.isAssignableFrom(target
- .getType());
- }
-
- private void addError(List<Throwable> errors, FrameworkField field,
- String suffix) {
- String message= "The @" + fAnnotation.getSimpleName() + " '"
- + field.getName() + "' " + suffix;
- errors.add(new Exception(message));
- }
+public class RuleMemberValidator {
+ /**
+ * Validates fields with a {@link ClassRule} annotation.
+ */
+ public static final RuleMemberValidator CLASS_RULE_VALIDATOR =
+ classRuleValidatorBuilder()
+ .withValidator(new DeclaringClassMustBePublic())
+ .withValidator(new MemberMustBeStatic())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new FieldMustBeATestRule())
+ .build();
+ /**
+ * Validates fields with a {@link Rule} annotation.
+ */
+ public static final RuleMemberValidator RULE_VALIDATOR =
+ testRuleValidatorBuilder()
+ .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new FieldMustBeARule())
+ .build();
+ /**
+ * Validates methods with a {@link ClassRule} annotation.
+ */
+ public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR =
+ classRuleValidatorBuilder()
+ .forMethods()
+ .withValidator(new DeclaringClassMustBePublic())
+ .withValidator(new MemberMustBeStatic())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new MethodMustBeATestRule())
+ .build();
+
+ /**
+ * Validates methods with a {@link Rule} annotation.
+ */
+ public static final RuleMemberValidator RULE_METHOD_VALIDATOR =
+ testRuleValidatorBuilder()
+ .forMethods()
+ .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
+ .withValidator(new MemberMustBePublic())
+ .withValidator(new MethodMustBeARule())
+ .build();
+
+ private final Class<? extends Annotation> annotation;
+ private final boolean methods;
+ private final List<RuleValidator> validatorStrategies;
+
+ RuleMemberValidator(Builder builder) {
+ this.annotation = builder.annotation;
+ this.methods = builder.methods;
+ this.validatorStrategies = builder.validators;
+ }
+
+ /**
+ * Validate the {@link org.junit.runners.model.TestClass} and adds reasons
+ * for rejecting the class to a list of errors.
+ *
+ * @param target the {@code TestClass} to validate.
+ * @param errors the list of errors.
+ */
+ public void validate(TestClass target, List<Throwable> errors) {
+ List<? extends FrameworkMember<?>> members = methods ? target.getAnnotatedMethods(annotation)
+ : target.getAnnotatedFields(annotation);
+
+ for (FrameworkMember<?> each : members) {
+ validateMember(each, errors);
+ }
+ }
+
+ private void validateMember(FrameworkMember<?> member, List<Throwable> errors) {
+ for (RuleValidator strategy : validatorStrategies) {
+ strategy.validate(member, annotation, errors);
+ }
+ }
+
+ private static Builder classRuleValidatorBuilder() {
+ return new Builder(ClassRule.class);
+ }
+
+ private static Builder testRuleValidatorBuilder() {
+ return new Builder(Rule.class);
+ }
+
+ private static class Builder {
+ private final Class<? extends Annotation> annotation;
+ private boolean methods;
+ private final List<RuleValidator> validators;
+
+ private Builder(Class<? extends Annotation> annotation) {
+ this.annotation = annotation;
+ this.methods = false;
+ this.validators = new ArrayList<RuleValidator>();
+ }
+
+ Builder forMethods() {
+ methods = true;
+ return this;
+ }
+
+ Builder withValidator(RuleValidator validator) {
+ validators.add(validator);
+ return this;
+ }
+
+ RuleMemberValidator build() {
+ return new RuleMemberValidator(this);
+ }
+ }
+
+ private static boolean isRuleType(FrameworkMember<?> member) {
+ return isMethodRule(member) || isTestRule(member);
+ }
+
+ private static boolean isTestRule(FrameworkMember<?> member) {
+ return TestRule.class.isAssignableFrom(member.getType());
+ }
+
+ private static boolean isMethodRule(FrameworkMember<?> member) {
+ return MethodRule.class.isAssignableFrom(member.getType());
+ }
+
+ /**
+ * Encapsulates a single piece of validation logic, used to determine if {@link org.junit.Rule} and
+ * {@link org.junit.ClassRule} annotations have been used correctly
+ */
+ interface RuleValidator {
+ /**
+ * Examine the given member and add any violations of the strategy's validation logic to the given list of errors
+ * @param member The member (field or member) to examine
+ * @param annotation The type of rule annotation on the member
+ * @param errors The list of errors to add validation violations to
+ */
+ void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors);
+ }
+
+ /**
+ * Requires the validated member to be non-static
+ */
+ private static final class MemberMustBeNonStaticOrAlsoClassRule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ boolean isMethodRuleMember = isMethodRule(member);
+ boolean isClassRuleAnnotated = (member.getAnnotation(ClassRule.class) != null);
+
+ // We disallow:
+ // - static MethodRule members
+ // - static @Rule annotated members
+ // - UNLESS they're also @ClassRule annotated
+ // Note that MethodRule cannot be annotated with @ClassRule
+ if (member.isStatic() && (isMethodRuleMember || !isClassRuleAnnotated)) {
+ String message;
+ if (isMethodRule(member)) {
+ message = "must not be static.";
+ } else {
+ message = "must not be static or it must be annotated with @ClassRule.";
+ }
+ errors.add(new ValidationError(member, annotation, message));
+ }
+ }
+ }
+
+ /**
+ * Requires the member to be static
+ */
+ private static final class MemberMustBeStatic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!member.isStatic()) {
+ errors.add(new ValidationError(member, annotation,
+ "must be static."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member's declaring class to be public
+ */
+ private static final class DeclaringClassMustBePublic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isDeclaringClassPublic(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must be declared in a public class."));
+ }
+ }
+
+ private boolean isDeclaringClassPublic(FrameworkMember<?> member) {
+ return Modifier.isPublic(member.getDeclaringClass().getModifiers());
+ }
+ }
+
+ /**
+ * Requires the member to be public
+ */
+ private static final class MemberMustBePublic implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!member.isPublic()) {
+ errors.add(new ValidationError(member, annotation,
+ "must be public."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member is a field implementing {@link org.junit.rules.MethodRule} or {@link org.junit.rules.TestRule}
+ */
+ private static final class FieldMustBeARule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isRuleType(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must implement MethodRule or TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Require the member to return an implementation of {@link org.junit.rules.MethodRule} or
+ * {@link org.junit.rules.TestRule}
+ */
+ private static final class MethodMustBeARule implements RuleValidator {
+ public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isRuleType(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must return an implementation of MethodRule or TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Require the member to return an implementation of {@link org.junit.rules.TestRule}
+ */
+ private static final class MethodMustBeATestRule implements RuleValidator {
+ public void validate(FrameworkMember<?> member,
+ Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isTestRule(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must return an implementation of TestRule."));
+ }
+ }
+ }
+
+ /**
+ * Requires the member is a field implementing {@link org.junit.rules.TestRule}
+ */
+ private static final class FieldMustBeATestRule implements RuleValidator {
+
+ public void validate(FrameworkMember<?> member,
+ Class<? extends Annotation> annotation, List<Throwable> errors) {
+ if (!isTestRule(member)) {
+ errors.add(new ValidationError(member, annotation,
+ "must implement TestRule."));
+ }
+ }
+ }
}