Skip to content

Commit 53f6af8

Browse files
authored
feat(test): add new MultipleAttemptRule, StdOutCaptureRule & StdErrCaptureRule JUnit 4 rules (#327)
* feat(test): add new MultipleAttemptRule JUnit 4 rule * feat(test): add new StdOutCaptureRule & StdErrCaptureRule JUnit 4 rule * fix: line separator failures from windows
1 parent 1ae2e58 commit 53f6af8

8 files changed

Lines changed: 642 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.testing.junit4;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import org.junit.rules.TestRule;
24+
import org.junit.runner.Description;
25+
import org.junit.runners.model.MultipleFailureException;
26+
import org.junit.runners.model.Statement;
27+
28+
/**
29+
* A JUnit rule that allows multiple attempts of a test execution before ultimately reporting
30+
* failure for the test. Attempts will be attempted with an exponential backoff which defaults to a
31+
* starting duration of 1 second.
32+
*
33+
* <p>If after the maximum number of attempts the test has still not succeeded, all failures will be
34+
* propagated as the result of the test allowing all errors to be visible (regardless if they are
35+
* the same failure or different ones).
36+
*
37+
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
38+
*
39+
* <p><i>Note: It is important that the field is public</i>
40+
*
41+
* <pre>{@code
42+
* @Rule
43+
* public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3);
44+
* }</pre>
45+
*
46+
* @see org.junit.Rule
47+
*/
48+
public final class MultipleAttemptsRule implements TestRule {
49+
private final long initialBackoffMillis;
50+
private final int maxAttemptCount;
51+
52+
/**
53+
* Construct a {@link MultipleAttemptsRule} which will attempt a test up to {@code attemptCount}
54+
* times before ultimately reporting failure of the test.
55+
*
56+
* <p>The initialBackoffMillis will be set to 1000L.
57+
*
58+
* @param maxAttemptCount max number of attempts before reporting failure, must be greater than 0
59+
* @see #MultipleAttemptsRule(int, long)
60+
*/
61+
public MultipleAttemptsRule(int maxAttemptCount) {
62+
this(maxAttemptCount, 1000L);
63+
}
64+
65+
/**
66+
* Construct a {@link MultipleAttemptsRule} which will attempt a test up to {@code attemptCount}
67+
* times before ultimately reporting failure of the test.
68+
*
69+
* <p>The {@code initialBackoffMillis} will be used as the first pause duration before
70+
* reattempting the test.
71+
*
72+
* @param maxAttemptCount max number of attempts before reporting failure, must be greater than 0
73+
* @param initialBackoffMillis initial duration in millis to wait between attempts, must be
74+
* greater than or equal to 0
75+
*/
76+
public MultipleAttemptsRule(int maxAttemptCount, long initialBackoffMillis) {
77+
checkArgument(maxAttemptCount > 0, "attemptCount must be > 0");
78+
checkArgument(initialBackoffMillis >= 0, "initialBackoffMillis must be >= 0");
79+
this.initialBackoffMillis = initialBackoffMillis;
80+
this.maxAttemptCount = maxAttemptCount;
81+
}
82+
83+
@Override
84+
public Statement apply(final Statement base, Description description) {
85+
return new Statement() {
86+
@Override
87+
public void evaluate() throws Throwable {
88+
List<Throwable> failures = new ArrayList<>();
89+
90+
long retryIntervalMillis = initialBackoffMillis;
91+
92+
for (int i = 1; i <= maxAttemptCount; i++) {
93+
try {
94+
base.evaluate();
95+
return;
96+
} catch (Throwable t) {
97+
failures.add(t);
98+
Thread.sleep(retryIntervalMillis);
99+
retryIntervalMillis *= 1.5f;
100+
}
101+
}
102+
103+
MultipleFailureException.assertEmpty(failures);
104+
}
105+
};
106+
}
107+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.testing.junit4;
18+
19+
import java.io.PrintStream;
20+
import org.junit.Rule;
21+
22+
/**
23+
* A JUnit rule that allows the capturing stderr (i.e. {@link System#err} during the scope of a
24+
* test.
25+
*
26+
* <p><i>Note: If some part of the system holds a reference System.err before this rule is loaded
27+
* into the test lifecycle there is no way for this rule to capture the output. Ensure this rule is
28+
* declared as high in your test file as possible, and ordered using {@link Rule#order()} before
29+
* other Rules if necessary.</i>
30+
*
31+
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
32+
*
33+
* <p><i>Note: It is important that the field is public</i>
34+
*
35+
* <pre>{@code
36+
* @Rule
37+
* public StdErrCaptureRule stdErrCaptureRule = new StdErrCaptureRule();
38+
* }</pre>
39+
*
40+
* @see org.junit.Rule
41+
* @see Rule#order()
42+
*/
43+
public final class StdErrCaptureRule extends StdXCaptureRule {
44+
45+
public StdErrCaptureRule() {}
46+
47+
@Override
48+
protected PrintStream getOriginal() {
49+
return System.err;
50+
}
51+
52+
@Override
53+
protected void set(PrintStream ps) {
54+
System.setErr(ps);
55+
}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.testing.junit4;
18+
19+
import java.io.PrintStream;
20+
import org.junit.Rule;
21+
22+
/**
23+
* A JUnit rule that allows the capturing stdout (i.e. {@link System#out} during the scope of a
24+
* test.
25+
*
26+
* <p><i>Note: If some part of the system holds a reference System.out before this rule is loaded
27+
* into the test lifecycle there is no way for this rule to capture the output. Ensure this rule is
28+
* declared as high in your test file as possible, and ordered using {@link Rule#order()} before
29+
* other Rules if necessary.</i>
30+
*
31+
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
32+
*
33+
* <p><i>Note: It is important that the field is public</i>
34+
*
35+
* <pre>{@code
36+
* @Rule
37+
* public StdOutCaptureRule stdOutCaptureRule = new StdOutCaptureRule();
38+
* }</pre>
39+
*
40+
* @see org.junit.Rule
41+
* @see Rule#order()
42+
*/
43+
public final class StdOutCaptureRule extends StdXCaptureRule {
44+
45+
public StdOutCaptureRule() {}
46+
47+
@Override
48+
protected PrintStream getOriginal() {
49+
return System.out;
50+
}
51+
52+
@Override
53+
protected void set(PrintStream ps) {
54+
System.setOut(ps);
55+
}
56+
}

0 commit comments

Comments
 (0)