Skip to content
Permalink
Browse files
feat(test): add new MultipleAttemptRule, StdOutCaptureRule & StdErrCa…
…ptureRule 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
  • Loading branch information
BenWhitehead committed Nov 10, 2020
1 parent a40c71a commit 60f4a66ad907f4a838536e405326869487468f35
@@ -0,0 +1,107 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.testing.junit4;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.ArrayList;
import java.util.List;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;

/**
* A JUnit rule that allows multiple attempts of a test execution before ultimately reporting
* failure for the test. Attempts will be attempted with an exponential backoff which defaults to a
* starting duration of 1 second.
*
* <p>If after the maximum number of attempts the test has still not succeeded, all failures will be
* propagated as the result of the test allowing all errors to be visible (regardless if they are
* the same failure or different ones).
*
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
*
* <p><i>Note: It is important that the field is public</i>
*
* <pre>{@code
* @Rule
* public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3);
* }</pre>
*
* @see org.junit.Rule
*/
public final class MultipleAttemptsRule implements TestRule {
private final long initialBackoffMillis;
private final int maxAttemptCount;

/**
* Construct a {@link MultipleAttemptsRule} which will attempt a test up to {@code attemptCount}
* times before ultimately reporting failure of the test.
*
* <p>The initialBackoffMillis will be set to 1000L.
*
* @param maxAttemptCount max number of attempts before reporting failure, must be greater than 0
* @see #MultipleAttemptsRule(int, long)
*/
public MultipleAttemptsRule(int maxAttemptCount) {
this(maxAttemptCount, 1000L);
}

/**
* Construct a {@link MultipleAttemptsRule} which will attempt a test up to {@code attemptCount}
* times before ultimately reporting failure of the test.
*
* <p>The {@code initialBackoffMillis} will be used as the first pause duration before
* reattempting the test.
*
* @param maxAttemptCount max number of attempts before reporting failure, must be greater than 0
* @param initialBackoffMillis initial duration in millis to wait between attempts, must be
* greater than or equal to 0
*/
public MultipleAttemptsRule(int maxAttemptCount, long initialBackoffMillis) {
checkArgument(maxAttemptCount > 0, "attemptCount must be > 0");
checkArgument(initialBackoffMillis >= 0, "initialBackoffMillis must be >= 0");
this.initialBackoffMillis = initialBackoffMillis;
this.maxAttemptCount = maxAttemptCount;
}

@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
List<Throwable> failures = new ArrayList<>();

long retryIntervalMillis = initialBackoffMillis;

for (int i = 1; i <= maxAttemptCount; i++) {
try {
base.evaluate();
return;
} catch (Throwable t) {
failures.add(t);
Thread.sleep(retryIntervalMillis);
retryIntervalMillis *= 1.5f;
}
}

MultipleFailureException.assertEmpty(failures);
}
};
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.testing.junit4;

import java.io.PrintStream;
import org.junit.Rule;

/**
* A JUnit rule that allows the capturing stderr (i.e. {@link System#err} during the scope of a
* test.
*
* <p><i>Note: If some part of the system holds a reference System.err before this rule is loaded
* into the test lifecycle there is no way for this rule to capture the output. Ensure this rule is
* declared as high in your test file as possible, and ordered using {@link Rule#order()} before
* other Rules if necessary.</i>
*
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
*
* <p><i>Note: It is important that the field is public</i>
*
* <pre>{@code
* @Rule
* public StdErrCaptureRule stdErrCaptureRule = new StdErrCaptureRule();
* }</pre>
*
* @see org.junit.Rule
* @see Rule#order()
*/
public final class StdErrCaptureRule extends StdXCaptureRule {

public StdErrCaptureRule() {}

@Override
protected PrintStream getOriginal() {
return System.err;
}

@Override
protected void set(PrintStream ps) {
System.setErr(ps);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.testing.junit4;

import java.io.PrintStream;
import org.junit.Rule;

/**
* A JUnit rule that allows the capturing stdout (i.e. {@link System#out} during the scope of a
* test.
*
* <p><i>Note: If some part of the system holds a reference System.out before this rule is loaded
* into the test lifecycle there is no way for this rule to capture the output. Ensure this rule is
* declared as high in your test file as possible, and ordered using {@link Rule#order()} before
* other Rules if necessary.</i>
*
* <p>To use this rule add the field declaration to your JUnit 4 Test class:
*
* <p><i>Note: It is important that the field is public</i>
*
* <pre>{@code
* @Rule
* public StdOutCaptureRule stdOutCaptureRule = new StdOutCaptureRule();
* }</pre>
*
* @see org.junit.Rule
* @see Rule#order()
*/
public final class StdOutCaptureRule extends StdXCaptureRule {

public StdOutCaptureRule() {}

@Override
protected PrintStream getOriginal() {
return System.out;
}

@Override
protected void set(PrintStream ps) {
System.setOut(ps);
}
}

0 comments on commit 60f4a66

Please sign in to comment.