Skip to content

Commit eba6a49

Browse files
committed
Merge remote-tracking branch 'origin/GP-2306_Dan_testRepeated--SQUASHED'
2 parents 3058631 + bc4742c commit eba6a49

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package generic.test;
1717

18-
import static org.junit.Assert.*;
18+
import static org.junit.Assert.assertNotNull;
1919

2020
import java.awt.*;
2121
import java.awt.event.*;
@@ -43,6 +43,7 @@
4343
import org.junit.runner.Description;
4444

4545
import generic.jar.ResourceFile;
46+
import generic.test.rule.RepeatedTestRule;
4647
import generic.util.WindowUtilities;
4748
import ghidra.GhidraTestApplicationLayout;
4849
import ghidra.framework.Application;
@@ -113,6 +114,17 @@ protected void succeeded(Description description) {
113114
@Rule
114115
public RuleChain ruleChain = RuleChain.outerRule(testName).around(watchman);// control rule ordering
115116

117+
/**
118+
* This rule handles the {@link Repeated} annotation
119+
*
120+
* <p>
121+
* During batch mode, this rule should never be needed. This rule is included here as a
122+
* convenience, in case a developer wants to use the {@link Repeated} annotation to diagnose a
123+
* non-deterministic test failure. Without this rule, the annotation would be silently ignored.
124+
*/
125+
@Rule
126+
public TestRule repeatedRule = new RepeatedTestRule();
127+
116128
private void debugBatch(String message) {
117129
if (BATCH_MODE) {
118130
Msg.debug(AbstractGenericTest.class, message);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* ###
2+
* IP: Apache License 2.0
3+
*/
4+
package generic.test.rule;
5+
6+
import java.lang.annotation.*;
7+
8+
/**
9+
* Repeat the annotated test method some number of times
10+
*
11+
* <p>
12+
* As a matter of practice, no test should ever be committed into source control with this
13+
* annotation. It is only a tool for diagnosing non-deterministic test failures on the developer's
14+
* workstation. For example, suppose {@code testSomeMethod} fails every other Tuesday on the CI
15+
* system, but never seems to fail on the developer's workstation. It might help to repeat a test
16+
* 100 times, including its set-up and tear-down, in a single test run. To do this, the developer
17+
* can temporarily add the {@code @Repeated(100)} annotation, and then run the test from their IDE.
18+
*
19+
* <pre>
20+
* &#64;Test
21+
* &#64;Repeated(100)
22+
* public void testSomeMethod() {
23+
* SomeClass obj = new SomeClass();
24+
* obj.someMethod();
25+
* assertEquals(3, obj.getSomeState());
26+
* }
27+
* </pre>
28+
*
29+
* <p>
30+
* The number of repetitions can be adjusted depending on the desired level of assurance. If the
31+
* failure is truly due to timing, and not some other condition unique to the CI system, then it
32+
* will likely fail within 100 repetitions. Once the code is fixed, and the test passes for the
33+
* desired number of repetitions, the annotation should be removed before the changes are committed.
34+
*/
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target(ElementType.METHOD)
37+
public @interface Repeated {
38+
/**
39+
* The number of times to repeat the test, must be positive
40+
*
41+
* @return the count
42+
*/
43+
int value();
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* ###
2+
* IP: Apache License 2.0
3+
*/
4+
package generic.test.rule;
5+
6+
import java.util.Date;
7+
8+
import org.junit.runner.Description;
9+
import org.junit.runners.model.Statement;
10+
11+
import ghidra.util.Msg;
12+
13+
/**
14+
* A JUnit test statement that repeats its base statement 1 or more times
15+
*
16+
* @see Repeated
17+
*/
18+
public class RepeatedStatement extends Statement {
19+
private final Statement base;
20+
private final Description description;
21+
private final int count;
22+
23+
/**
24+
* Construct the statement
25+
*
26+
* @param base the base statement to repeat
27+
* @param description the description of the test
28+
* @param count the number of repetitions, must be positive
29+
*/
30+
public RepeatedStatement(Statement base, Description description, int count) {
31+
if (count <= 0) {
32+
throw new IllegalArgumentException(
33+
"@Repeated count must be positive. To ignore a test. Use @Ignore");
34+
}
35+
this.base = base;
36+
this.description = description;
37+
this.count = count;
38+
}
39+
40+
@Override
41+
public void evaluate() throws Throwable {
42+
for (int i = 0; i < count; i++) {
43+
if (count > 1) {
44+
Msg.debug(this,
45+
(new Date()) + "\n *** REPETITION " + (i + 1) + "/" + count + " of " +
46+
description.getDisplayName() + " ***");
47+
}
48+
base.evaluate();
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* ###
2+
* IP: Apache License 2.0
3+
*/
4+
package generic.test.rule;
5+
6+
import org.junit.Rule;
7+
import org.junit.rules.TestRule;
8+
import org.junit.runner.Description;
9+
import org.junit.runners.model.Statement;
10+
11+
import generic.test.AbstractGenericTest;
12+
13+
/**
14+
* A test rule which processes the {@link Repeated} annotation
15+
*
16+
* <p>
17+
* This must be included in your test case (or a superclass) as a field with the {@link Rule}
18+
* annotation. It's included in {@link AbstractGenericTest}, so most Ghidra test classes already
19+
* have it.
20+
*/
21+
public class RepeatedTestRule implements TestRule {
22+
@Override
23+
public Statement apply(Statement base, Description description) {
24+
Repeated annot = description.getAnnotation(Repeated.class);
25+
if (annot == null) {
26+
return base;
27+
}
28+
return new RepeatedStatement(base, description, annot.value());
29+
}
30+
}

0 commit comments

Comments
 (0)