Skip to content

Commit 4083423

Browse files
authored
Merge pull request #1692 from tulinkry/conditional-repeatable
making the conditional rules in tests repeatable
2 parents 6ced2cb + a49aaa4 commit 4083423

File tree

6 files changed

+257
-37
lines changed

6 files changed

+257
-37
lines changed

test/org/opensolaris/opengrok/condition/ConditionalRun.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,27 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
2222
*/
2323
package org.opensolaris.opengrok.condition;
2424

2525
import java.lang.annotation.Documented;
2626
import java.lang.annotation.ElementType;
27+
import java.lang.annotation.Inherited;
28+
import java.lang.annotation.Repeatable;
2729
import java.lang.annotation.Retention;
2830
import java.lang.annotation.RetentionPolicy;
2931
import java.lang.annotation.Target;
3032

33+
/**
34+
* Repeatable annotation for automatic test skipping.
35+
*/
3136
@Retention(RetentionPolicy.RUNTIME)
32-
@Target(value = {ElementType.METHOD, ElementType.TYPE})
37+
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
38+
@Repeatable(ConditionalRunRepeatable.class)
39+
@Inherited
3340
@Documented
3441
public @interface ConditionalRun {
42+
3543
Class<? extends RunCondition> condition();
3644
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
package org.opensolaris.opengrok.condition;
24+
25+
import java.lang.annotation.Documented;
26+
import java.lang.annotation.ElementType;
27+
import java.lang.annotation.Inherited;
28+
import java.lang.annotation.Retention;
29+
import java.lang.annotation.Target;
30+
31+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
32+
33+
/**
34+
* Annotation allowing the {@link ConditionalRun} to be repeatable annotation.
35+
*
36+
* @author Krystof Tulinger
37+
*/
38+
@Retention(value = RUNTIME)
39+
@Documented
40+
@Inherited
41+
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
42+
public @interface ConditionalRunRepeatable {
43+
44+
ConditionalRun[] value();
45+
}

test/org/opensolaris/opengrok/condition/ConditionalRunRule.java

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
2222
*/
2323
package org.opensolaris.opengrok.condition;
2424

25+
import java.lang.reflect.Method;
26+
import java.lang.reflect.Modifier;
27+
import java.util.ArrayList;
28+
import java.util.LinkedList;
29+
import java.util.List;
2530
import org.junit.Assume;
2631
import org.junit.rules.TestRule;
2732
import org.junit.runner.Description;
2833
import org.junit.runners.model.Statement;
2934

30-
import java.lang.reflect.Modifier;
31-
3235
/**
3336
*
3437
* This rule can be added to a Junit test and will look for the annotation
@@ -41,6 +44,7 @@
4144
* https://gist.github.com/yinzara/9980184
4245
* http://cwd.dhemery.com/2010/12/junit-rules/
4346
* http://stackoverflow.com/questions/28145735/androidjunit4-class-org-junit-assume-assumetrue-assumptionviolatedexception/
47+
* https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html
4448
*/
4549
public class ConditionalRunRule implements TestRule {
4650

@@ -64,34 +68,70 @@ public Statement apply(Statement aStatement, Description aDescription) {
6468
}
6569

6670
private static boolean hasConditionalIgnoreAnnotationOnClass(Description aDescription) {
67-
return aDescription.getTestClass().getAnnotation(ConditionalRun.class) != null;
71+
return aDescription.getTestClass().getAnnotationsByType(ConditionalRun.class).length > 0;
6872
}
6973

7074
private static RunCondition getIgnoreConditionOnClass(Description aDescription) {
71-
ConditionalRun annotation = aDescription.getTestClass().getAnnotation(ConditionalRun.class);
72-
return new IgnoreConditionCreator(aDescription.getTestClass(), annotation).create();
75+
ConditionalRun[] annotations = aDescription.getTestClass().getAnnotationsByType(ConditionalRun.class);
76+
return new IgnoreConditionCreator(aDescription.getTestClass(), annotations).create();
7377
}
7478

7579
private static boolean hasConditionalIgnoreAnnotationOnMethod(Description aDescription) {
76-
return aDescription.getAnnotation(ConditionalRun.class) != null;
80+
try {
81+
// this is possible because test methods must not have any argument
82+
Method testMethod = aDescription.getTestClass().getMethod(aDescription.getMethodName());
83+
return testMethod.getAnnotationsByType(ConditionalRun.class).length > 0;
84+
} catch (NoSuchMethodException | SecurityException ex) {
85+
throw new RuntimeException(ex);
86+
}
7787
}
7888

7989
private static RunCondition getIgnoreConditionOnMethod(Description aDescription) {
80-
ConditionalRun annotation = aDescription.getAnnotation(ConditionalRun.class);
81-
return new IgnoreConditionCreator(aDescription.getTestClass(), annotation).create();
90+
try {
91+
// this is possible because test methods must not have any argument
92+
ConditionalRun[] annotations = aDescription.getTestClass().getMethod(aDescription.getMethodName()).getAnnotationsByType(ConditionalRun.class);
93+
return new IgnoreConditionCreator(aDescription.getTestClass(), annotations).create();
94+
} catch (NoSuchMethodException | SecurityException ex) {
95+
throw new RuntimeException(ex);
96+
}
8297
}
8398

84-
private static class IgnoreConditionCreator {
99+
/**
100+
* Container for several conditions joined by an AND operator.
101+
*/
102+
protected static class CompositeCondition implements RunCondition {
103+
104+
List<RunCondition> conditions = new LinkedList<>();
105+
106+
public boolean add(RunCondition e) {
107+
return conditions.add(e);
108+
}
109+
110+
@Override
111+
public boolean isSatisfied() {
112+
for (RunCondition condition : conditions) {
113+
if (!condition.isSatisfied()) {
114+
return false;
115+
}
116+
}
117+
return true;
118+
}
119+
}
120+
121+
protected static class IgnoreConditionCreator {
85122

86123
private final Class<?> mTestClass;
87-
private final Class<? extends RunCondition> conditionType;
124+
private final List<Class<? extends RunCondition>> conditionTypes;
88125

89-
IgnoreConditionCreator(Class<?> aTestClass, ConditionalRun annotation) {
126+
public IgnoreConditionCreator(Class<?> aTestClass, ConditionalRun[] annotation) {
90127
this.mTestClass = aTestClass;
91-
this.conditionType = annotation.condition();
128+
this.conditionTypes = new ArrayList<>(annotation.length);
129+
for (int i = 0; i < annotation.length; i++) {
130+
this.conditionTypes.add(i, annotation[i].condition());
131+
}
92132
}
93133

94-
RunCondition create() {
134+
public RunCondition create() {
95135
checkConditionType();
96136
try {
97137
return createCondition();
@@ -103,38 +143,49 @@ RunCondition create() {
103143
}
104144

105145
private RunCondition createCondition() throws Exception {
106-
RunCondition result;
107-
if (isConditionTypeStandalone()) {
108-
result = conditionType.newInstance();
109-
} else {
110-
result = conditionType.getDeclaredConstructor(mTestClass).newInstance(mTestClass);
146+
CompositeCondition result = null;
147+
/**
148+
* Run through the list of classes implementing RunCondition and
149+
* create a new class from it.
150+
*/
151+
for (Class<? extends RunCondition> clazz : conditionTypes) {
152+
if (result == null) {
153+
result = new CompositeCondition();
154+
}
155+
if (isConditionTypeStandalone(clazz)) {
156+
result.add(clazz.newInstance());
157+
} else {
158+
result.add(clazz.getDeclaredConstructor(mTestClass).newInstance(mTestClass));
159+
}
111160
}
112161
return result;
113162
}
114163

115164
private void checkConditionType() {
116-
if (!isConditionTypeStandalone() && !isConditionTypeDeclaredInTarget()) {
117-
String msg
118-
= "Conditional class '%s' is a member class "
119-
+ "but was not declared inside the test case using it.\n"
120-
+ "Either make this class a static class, "
121-
+ "standalone class (by declaring it in it's own file) "
122-
+ "or move it inside the test case using it";
123-
throw new IllegalArgumentException(String.format(msg, conditionType.getName()));
165+
for (Class<? extends RunCondition> clazz : conditionTypes) {
166+
if (!isConditionTypeStandalone(clazz) && !isConditionTypeDeclaredInTarget(clazz)) {
167+
String msg
168+
= "Conditional class '%s' is a member class "
169+
+ "but was not declared inside the test case using it.\n"
170+
+ "Either make this class a static class, "
171+
+ "standalone class (by declaring it in it's own file) "
172+
+ "or move it inside the test case using it";
173+
throw new IllegalArgumentException(String.format(msg, clazz.getName()));
174+
}
124175
}
125176
}
126177

127-
private boolean isConditionTypeStandalone() {
128-
return !conditionType.isMemberClass()
129-
|| Modifier.isStatic(conditionType.getModifiers());
178+
private boolean isConditionTypeStandalone(Class<? extends RunCondition> clazz) {
179+
return !clazz.isMemberClass()
180+
|| Modifier.isStatic(clazz.getModifiers());
130181
}
131182

132-
private boolean isConditionTypeDeclaredInTarget() {
133-
return mTestClass.getClass().isAssignableFrom(conditionType.getDeclaringClass());
183+
private boolean isConditionTypeDeclaredInTarget(Class<? extends RunCondition> clazz) {
184+
return mTestClass.getClass().isAssignableFrom(clazz.getDeclaringClass());
134185
}
135186
}
136187

137-
private static class IgnoreStatement extends Statement {
188+
protected static class IgnoreStatement extends Statement {
138189

139190
private final RunCondition condition;
140191

test/org/opensolaris/opengrok/condition/RunCondition.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
*/
1919

2020
/*
21-
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
21+
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
2222
*/
2323
package org.opensolaris.opengrok.condition;
2424

2525
public interface RunCondition {
2626

27-
boolean isSatisfied();
27+
public boolean isSatisfied();
2828
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
package org.opensolaris.opengrok.condition;
24+
25+
import org.junit.Assert;
26+
import org.junit.Rule;
27+
import org.junit.Test;
28+
29+
/**
30+
*
31+
* @author Krystof Tulinger
32+
*/
33+
@ConditionalRun(condition = RunningRepeatableConditionTest.TrueRunCondition.class)
34+
@ConditionalRun(condition = RunningRepeatableConditionTest.TrueRunCondition.class)
35+
@ConditionalRun(condition = RunningRepeatableConditionTest.TrueRunCondition.class)
36+
public class RunningRepeatableConditionTest {
37+
38+
@Rule
39+
public ConditionalRunRule rule = new ConditionalRunRule();
40+
41+
@ConditionalRun(condition = TrueRunCondition.class)
42+
@ConditionalRun(condition = TrueRunCondition.class)
43+
@ConditionalRun(condition = TrueRunCondition.class)
44+
@ConditionalRun(condition = TrueRunCondition.class)
45+
@ConditionalRun(condition = TrueRunCondition.class)
46+
@Test
47+
public void testRunningTest() {
48+
Assert.assertTrue("This test shall run", true);
49+
}
50+
51+
@ConditionalRun(condition = TrueRunCondition.class)
52+
@ConditionalRun(condition = FalseRunCondition.class)
53+
@ConditionalRun(condition = TrueRunCondition.class)
54+
@Test
55+
public void testSkippedTest() throws NoSuchMethodException {
56+
Assert.assertTrue("This test must be skipped", false);
57+
}
58+
59+
protected static class TrueRunCondition implements RunCondition {
60+
61+
@Override
62+
public boolean isSatisfied() {
63+
return true;
64+
}
65+
}
66+
67+
protected static class FalseRunCondition implements RunCondition {
68+
69+
@Override
70+
public boolean isSatisfied() {
71+
return false;
72+
}
73+
}
74+
75+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
package org.opensolaris.opengrok.condition;
24+
25+
import org.junit.Assert;
26+
import org.junit.Rule;
27+
import org.junit.Test;
28+
29+
@ConditionalRun(condition = RunningRepeatableConditionTest.TrueRunCondition.class)
30+
@ConditionalRun(condition = RunningRepeatableConditionTest.FalseRunCondition.class)
31+
@ConditionalRun(condition = RunningRepeatableConditionTest.TrueRunCondition.class)
32+
public class SkippingRepeatableConditionTest {
33+
34+
@Rule
35+
public ConditionalRunRule rule = new ConditionalRunRule();
36+
37+
@Test
38+
public void testSkippedTest() throws NoSuchMethodException {
39+
Assert.assertTrue("This test must be skipped", false);
40+
}
41+
}

0 commit comments

Comments
 (0)