This is an automated email from the ASF dual-hosted git repository. chesnay pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/flink.git
commit 862ae89f601dabea349f0e5183b47f2be7580d8d Author: Chesnay Schepler <[email protected]> AuthorDate: Wed Apr 21 14:57:19 2021 +0200 [FLINK-20723][tests] Ignore expected exception when retrying --- .../apache/flink/testutils/junit/RetryRule.java | 52 +++++++++---- .../flink/testutils/junit/RetryRuleTest.java | 88 ++++++++++++++++++++++ 2 files changed, 126 insertions(+), 14 deletions(-) diff --git a/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/testutils/junit/RetryRule.java b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/testutils/junit/RetryRule.java index f4e9541..91da07a 100644 --- a/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/testutils/junit/RetryRule.java +++ b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/testutils/junit/RetryRule.java @@ -54,40 +54,51 @@ public class RetryRule implements TestRule { RetryOnFailure retryOnFailure = description.getAnnotation(RetryOnFailure.class); RetryOnException retryOnException = description.getAnnotation(RetryOnException.class); - // sanity check that we don't use expected exceptions with the RetryOnX annotations - if (retryOnFailure != null || retryOnException != null) { - Test test = description.getAnnotation(Test.class); - if (test.expected() != Test.None.class) { - throw new IllegalArgumentException( - "You cannot combine the RetryOnFailure " - + "annotation with the Test(expected) annotation."); - } - } - // sanity check that we don't use both annotations if (retryOnFailure != null && retryOnException != null) { throw new IllegalArgumentException( "You cannot combine the RetryOnFailure and RetryOnException annotations."); } + final Class<? extends Throwable> whitelistedException = getExpectedException(description); + if (retryOnFailure != null) { - return new RetryOnFailureStatement(retryOnFailure.times(), statement); + return new RetryOnFailureStatement( + retryOnFailure.times(), statement, whitelistedException); } else if (retryOnException != null) { return new RetryOnExceptionStatement( - retryOnException.times(), retryOnException.exception(), statement); + retryOnException.times(), + retryOnException.exception(), + statement, + whitelistedException); } else { return statement; } } + @Nullable + private static Class<? extends Throwable> getExpectedException(Description description) { + Test test = description.getAnnotation(Test.class); + if (test.expected() != Test.None.class) { + return test.expected(); + } + + return null; + } + /** Retries a test in case of a failure. */ private static class RetryOnFailureStatement extends Statement { private final int timesOnFailure; private final Statement statement; + @Nullable private final Class<? extends Throwable> expectedException; - private RetryOnFailureStatement(int timesOnFailure, Statement statement) { + private RetryOnFailureStatement( + int timesOnFailure, + Statement statement, + @Nullable Class<? extends Throwable> expectedException) { + this.expectedException = expectedException; if (timesOnFailure < 0) { throw new IllegalArgumentException("Negatives number of retries on failure"); } @@ -107,6 +118,11 @@ public class RetryRule implements TestRule { statement.evaluate(); break; // success } catch (Throwable t) { + if (expectedException != null + && expectedException.isAssignableFrom(t.getClass())) { + throw t; + } + LOG.warn( String.format( "Test run failed (%d/%d).", currentRun, timesOnFailure + 1), @@ -127,11 +143,13 @@ public class RetryRule implements TestRule { private final Class<? extends Throwable> exceptionClass; private final int timesOnFailure; private final Statement statement; + @Nullable private final Class<? extends Throwable> expectedException; private RetryOnExceptionStatement( int timesOnFailure, Class<? extends Throwable> exceptionClass, - Statement statement) { + Statement statement, + @Nullable Class<? extends Throwable> expectedException) { if (timesOnFailure < 0) { throw new IllegalArgumentException("Negatives number of retries on failure"); } @@ -142,6 +160,7 @@ public class RetryRule implements TestRule { this.exceptionClass = (exceptionClass); this.timesOnFailure = timesOnFailure; this.statement = statement; + this.expectedException = expectedException; } /** @@ -156,6 +175,11 @@ public class RetryRule implements TestRule { statement.evaluate(); break; // success } catch (Throwable t) { + if (expectedException != null + && expectedException.isAssignableFrom(t.getClass())) { + throw t; + } + LOG.warn( String.format( "Test run failed (%d/%d).", currentRun, timesOnFailure + 1), diff --git a/flink-test-utils-parent/flink-test-utils-junit/src/test/java/org/apache/flink/testutils/junit/RetryRuleTest.java b/flink-test-utils-parent/flink-test-utils-junit/src/test/java/org/apache/flink/testutils/junit/RetryRuleTest.java new file mode 100644 index 0000000..e2e3401 --- /dev/null +++ b/flink-test-utils-parent/flink-test-utils-junit/src/test/java/org/apache/flink/testutils/junit/RetryRuleTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.flink.testutils.junit; + +import org.apache.flink.util.TestLogger; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** Tests for the {@link RetryRule}. */ +public class RetryRuleTest extends TestLogger { + + @Test + public void testExpectedExceptionIgnored() throws Throwable { + final RetryRule retryRule = new RetryRule(); + + final Description testDescription = + Description.createTestDescription( + TestClassWithTestExpectingRuntimeException.class, + "test", + TestClassWithTestExpectingRuntimeException.class + .getMethod("test") + .getAnnotations()); + + final TestStatement statement = new TestStatement(1); + + try { + retryRule.apply(statement, testDescription).evaluate(); + Assert.fail("Should have failed."); + } catch (RuntimeException expected) { + } + + assertThat(statement.getNumEvaluations(), is(1)); + } + + @Ignore // we don't want to actually this run as a test + private static class TestClassWithTestExpectingRuntimeException { + @RetryOnFailure(times = 2) + @Test(expected = RuntimeException.class) + public void test() {} + } + + private static class TestStatement extends Statement { + private final int numEvaluationsToFail; + + private int numEvaluations = 0; + + private TestStatement(int numEvaluationsToFail) { + this.numEvaluationsToFail = numEvaluationsToFail; + } + + @Override + public void evaluate() throws Throwable { + try { + if (numEvaluations < numEvaluationsToFail) { + throw new RuntimeException("test exception"); + } + } finally { + numEvaluations++; + } + } + + public int getNumEvaluations() { + return numEvaluations; + } + } +}
