Skip to content

Commit d9b90bc

Browse files
committed
Merge pull request #932 from rowanhill/non-static-class-rules
Allow members annotated with both @ClassRule and @rule to be static
2 parents df00d5e + 15c925c commit d9b90bc

14 files changed

+420
-144
lines changed

Diff for: src/main/java/org/junit/internal/runners/rules/RuleFieldValidator.java

-128
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package org.junit.internal.runners.rules;
2+
3+
import org.junit.ClassRule;
4+
import org.junit.Rule;
5+
import org.junit.rules.MethodRule;
6+
import org.junit.rules.TestRule;
7+
import org.junit.runners.model.FrameworkMember;
8+
import org.junit.runners.model.TestClass;
9+
10+
import java.lang.annotation.Annotation;
11+
import java.lang.reflect.Modifier;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* A RuleMemberValidator validates the rule fields/methods of a
17+
* {@link org.junit.runners.model.TestClass}. All reasons for rejecting the
18+
* {@code TestClass} are written to a list of errors.
19+
*
20+
* <p>There are four slightly different validators. The {@link #CLASS_RULE_VALIDATOR}
21+
* validates fields with a {@link ClassRule} annotation and the
22+
* {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.</p>
23+
*
24+
* <p>The {@link #CLASS_RULE_METHOD_VALIDATOR}
25+
* validates methods with a {@link ClassRule} annotation and the
26+
* {@link #RULE_METHOD_VALIDATOR} validates methods with a {@link Rule} annotation.</p>
27+
*/
28+
public class RuleMemberValidator {
29+
/**
30+
* Validates fields with a {@link ClassRule} annotation.
31+
*/
32+
public static final RuleMemberValidator CLASS_RULE_VALIDATOR =
33+
classRuleValidatorBuilder()
34+
.withValidator(new DeclaringClassMustBePublic())
35+
.withValidator(new MemberMustBeStatic())
36+
.withValidator(new MemberMustBePublic())
37+
.withValidator(new FieldMustBeARule())
38+
.build();
39+
/**
40+
* Validates fields with a {@link Rule} annotation.
41+
*/
42+
public static final RuleMemberValidator RULE_VALIDATOR =
43+
testRuleValidatorBuilder()
44+
.withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
45+
.withValidator(new MemberMustBePublic())
46+
.withValidator(new FieldMustBeARule())
47+
.build();
48+
/**
49+
* Validates methods with a {@link ClassRule} annotation.
50+
*/
51+
public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR =
52+
classRuleValidatorBuilder()
53+
.forMethods()
54+
.withValidator(new DeclaringClassMustBePublic())
55+
.withValidator(new MemberMustBeStatic())
56+
.withValidator(new MemberMustBePublic())
57+
.withValidator(new MethodMustBeARule())
58+
.build();
59+
60+
/**
61+
* Validates methods with a {@link Rule} annotation.
62+
*/
63+
public static final RuleMemberValidator RULE_METHOD_VALIDATOR =
64+
testRuleValidatorBuilder()
65+
.forMethods()
66+
.withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
67+
.withValidator(new MemberMustBePublic())
68+
.withValidator(new MethodMustBeARule())
69+
.build();
70+
71+
private final Class<? extends Annotation> annotation;
72+
private final boolean methods;
73+
private final List<RuleValidator> validatorStrategies;
74+
75+
RuleMemberValidator(Builder builder) {
76+
this.annotation = builder.annotation;
77+
this.methods = builder.methods;
78+
this.validatorStrategies = builder.validators;
79+
}
80+
81+
/**
82+
* Validate the {@link org.junit.runners.model.TestClass} and adds reasons
83+
* for rejecting the class to a list of errors.
84+
*
85+
* @param target the {@code TestClass} to validate.
86+
* @param errors the list of errors.
87+
*/
88+
public void validate(TestClass target, List<Throwable> errors) {
89+
List<? extends FrameworkMember<?>> members = methods ? target.getAnnotatedMethods(annotation)
90+
: target.getAnnotatedFields(annotation);
91+
92+
for (FrameworkMember<?> each : members) {
93+
validateMember(each, errors);
94+
}
95+
}
96+
97+
private void validateMember(FrameworkMember<?> member, List<Throwable> errors) {
98+
for (RuleValidator strategy : validatorStrategies) {
99+
strategy.validate(member, annotation, errors);
100+
}
101+
}
102+
103+
private static Builder classRuleValidatorBuilder() {
104+
return new Builder(ClassRule.class);
105+
}
106+
107+
private static Builder testRuleValidatorBuilder() {
108+
return new Builder(Rule.class);
109+
}
110+
111+
private static class Builder {
112+
private final Class<? extends Annotation> annotation;
113+
private boolean methods;
114+
private final List<RuleValidator> validators;
115+
116+
private Builder(Class<? extends Annotation> annotation) {
117+
this.annotation = annotation;
118+
this.methods = false;
119+
this.validators = new ArrayList<RuleValidator>();
120+
}
121+
122+
Builder forMethods() {
123+
methods = true;
124+
return this;
125+
}
126+
127+
Builder withValidator(RuleValidator validator) {
128+
validators.add(validator);
129+
return this;
130+
}
131+
132+
RuleMemberValidator build() {
133+
return new RuleMemberValidator(this);
134+
}
135+
}
136+
137+
private static boolean isRuleType(FrameworkMember<?> member) {
138+
return isMethodRule(member) || isTestRule(member);
139+
}
140+
141+
private static boolean isTestRule(FrameworkMember<?> member) {
142+
return TestRule.class.isAssignableFrom(member.getType());
143+
}
144+
145+
private static boolean isMethodRule(FrameworkMember<?> member) {
146+
return MethodRule.class.isAssignableFrom(member.getType());
147+
}
148+
149+
/**
150+
* Encapsulates a single piece of validation logic, used to determine if {@link org.junit.Rule} and
151+
* {@link org.junit.ClassRule} annotations have been used correctly
152+
*/
153+
interface RuleValidator {
154+
/**
155+
* Examine the given member and add any violations of the strategy's validation logic to the given list of errors
156+
* @param member The member (field or member) to examine
157+
* @param annotation The type of rule annotation on the member
158+
* @param errors The list of errors to add validation violations to
159+
*/
160+
void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors);
161+
}
162+
163+
/**
164+
* Requires the validated member to be non-static
165+
*/
166+
private static final class MemberMustBeNonStaticOrAlsoClassRule implements RuleValidator {
167+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
168+
boolean isMethodRuleMember = isMethodRule(member);
169+
boolean isClassRuleAnnotated = (member.getAnnotation(ClassRule.class) != null);
170+
171+
// We disallow:
172+
// - static MethodRule members
173+
// - static @Rule annotated members
174+
// - UNLESS they're also @ClassRule annotated
175+
// Note that MethodRule cannot be annotated with @ClassRule
176+
if (member.isStatic() && (isMethodRuleMember || !isClassRuleAnnotated)) {
177+
String message;
178+
if (isMethodRule(member)) {
179+
message = "must not be static.";
180+
} else {
181+
message = "must not be static or it must be annotated with @ClassRule.";
182+
}
183+
errors.add(new ValidationError(member, annotation, message));
184+
}
185+
}
186+
}
187+
188+
/**
189+
* Requires the member to be static
190+
*/
191+
private static final class MemberMustBeStatic implements RuleValidator {
192+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
193+
if (!member.isStatic()) {
194+
errors.add(new ValidationError(member, annotation,
195+
"must be static."));
196+
}
197+
}
198+
}
199+
200+
/**
201+
* Requires the member's declaring class to be public
202+
*/
203+
private static final class DeclaringClassMustBePublic implements RuleValidator {
204+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
205+
if (!isDeclaringClassPublic(member)) {
206+
errors.add(new ValidationError(member, annotation,
207+
"must be declared in a public class."));
208+
}
209+
}
210+
211+
private boolean isDeclaringClassPublic(FrameworkMember<?> member) {
212+
return Modifier.isPublic(member.getDeclaringClass().getModifiers());
213+
}
214+
}
215+
216+
/**
217+
* Requires the member to be public
218+
*/
219+
private static final class MemberMustBePublic implements RuleValidator {
220+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
221+
if (!member.isPublic()) {
222+
errors.add(new ValidationError(member, annotation,
223+
"must be public."));
224+
}
225+
}
226+
}
227+
228+
/**
229+
* Requires the member is a field implementing {@link org.junit.rules.MethodRule} or {@link org.junit.rules.TestRule}
230+
*/
231+
private static final class FieldMustBeARule implements RuleValidator {
232+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
233+
if (!isRuleType(member)) {
234+
errors.add(new ValidationError(member, annotation,
235+
"must implement MethodRule or TestRule."));
236+
}
237+
}
238+
}
239+
240+
/**
241+
* Require the member to return an implementation of {@link org.junit.rules.MethodRule} or
242+
* {@link org.junit.rules.TestRule}
243+
*/
244+
private static final class MethodMustBeARule implements RuleValidator {
245+
public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) {
246+
if (!isRuleType(member)) {
247+
errors.add(new ValidationError(member, annotation,
248+
"must return an implementation of MethodRule or TestRule."));
249+
}
250+
}
251+
}
252+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.junit.internal.runners.rules;
2+
3+
import org.junit.runners.model.FrameworkMember;
4+
5+
import java.lang.annotation.Annotation;
6+
7+
class ValidationError extends Exception {
8+
public ValidationError(FrameworkMember<?> member, Class<? extends Annotation> annotation, String suffix) {
9+
super(String.format("The @%s '%s' %s", annotation.getSimpleName(), member.getName(), suffix));
10+
}
11+
}

0 commit comments

Comments
 (0)