Skip to content

Commit

Permalink
Fail fast conditions can now be specified with assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
johanhaleby committed Mar 4, 2022
1 parent d39c00d commit fc45eb7
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 27 deletions.
33 changes: 31 additions & 2 deletions awaitility/src/main/java/org/awaitility/Awaitility.java
Expand Up @@ -18,6 +18,8 @@
import org.awaitility.constraint.AtMostWaitConstraint;
import org.awaitility.constraint.WaitConstraint;
import org.awaitility.core.*;
import org.awaitility.core.FailFastCondition.CallableFailFastCondition;
import org.awaitility.core.FailFastCondition.CallableFailFastCondition.FailFastAssertion;
import org.awaitility.pollinterval.FixedPollInterval;
import org.awaitility.pollinterval.PollInterval;
import org.hamcrest.Matcher;
Expand Down Expand Up @@ -481,7 +483,34 @@ public static void setDefaultConditionEvaluationListener(ConditionEvaluationList
* @see #setDefaultFailFastCondition(String, Callable)
*/
public static void setDefaultFailFastCondition(Callable<Boolean> defaultFailFastCondition) {
Awaitility.defaultFailFastCondition = new FailFastCondition(null, defaultFailFastCondition);
Awaitility.defaultFailFastCondition = new CallableFailFastCondition(null, defaultFailFastCondition);
}

/**
* If the supplied <code>failFastAssertion</code> <i>ever</i> returns throws an exception, it indicates our condition will <i>never</i> be true, and if so fail the system immediately.
* This allows you to use a more descriptive error message of why the fail-fast condition failed instead of using {@link #setDefaultFailFastCondition(String, Callable)}.
*
* @param defaultFailFastAssertion The terminal failure assertion
* @return the condition factory
* @see #setDefaultFailFastCondition(Callable)
* @see ConditionFactory#failFast(ThrowingRunnable)
*/
public static void setDefaultFailFastCondition(ThrowingRunnable defaultFailFastAssertion) {
setDefaultFailFastCondition(null, defaultFailFastAssertion);
}

/**
* If the supplied <code>failFastAssertion</code> <i>ever</i> returns throws an exception, it indicates our condition will <i>never</i> be true, and if so fail the system immediately.
* This allows you to use a more descriptive error message of why the fail-fast condition failed instead of using {@link #setDefaultFailFastCondition(String, Callable)}.
*
* @param failFastFailureReason A descriptive reason why the fail fast condition has failed, will be included in the {@link TerminalFailureException} thrown if <code>failFastAssertion</code> throws an exception.
* @param defaultFailFastAssertion The terminal failure assertion
* @return the condition factory
* @see #setDefaultFailFastCondition(Callable)
* @see ConditionFactory#failFast(ThrowingRunnable)
*/
public static void setDefaultFailFastCondition(String failFastFailureReason, ThrowingRunnable defaultFailFastAssertion) {
Awaitility.defaultFailFastCondition = new FailFastAssertion(failFastFailureReason, defaultFailFastAssertion);
}

/**
Expand All @@ -492,7 +521,7 @@ public static void setDefaultFailFastCondition(Callable<Boolean> defaultFailFast
* @param failFastFailureReason A descriptive reason why the fail fast condition has failed, will be included in the {@link TerminalFailureException} thrown if <code>failFastCondition</code> evaluates to <code>true</code>.
*/
public static void setDefaultFailFastCondition(String failFastFailureReason, Callable<Boolean> defaultFailFastCondition) {
Awaitility.defaultFailFastCondition = new FailFastCondition(failFastFailureReason, defaultFailFastCondition);
Awaitility.defaultFailFastCondition = new CallableFailFastCondition(failFastFailureReason, defaultFailFastCondition);
}

/**
Expand Down
24 changes: 20 additions & 4 deletions awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java
Expand Up @@ -16,6 +16,9 @@
package org.awaitility.core;


import org.awaitility.core.FailFastCondition.CallableFailFastCondition;
import org.awaitility.core.FailFastCondition.CallableFailFastCondition.FailFastAssertion;

import java.lang.Thread.UncaughtExceptionHandler;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
Expand Down Expand Up @@ -177,14 +180,27 @@ public <T> void await(final ConditionEvaluationHandler<T> conditionEvaluationHan
}
}

private void executeFailFastConditionIfDefined() throws Exception {
private void executeFailFastConditionIfDefined() throws Throwable {
FailFastCondition failFastCondition = conditionSettings.getFailFastCondition();
if (failFastCondition != null) {
Boolean terminalFailureReached = failFastCondition.getFailFastCondition().call();
if (failFastCondition == null) {
return;
}

if (failFastCondition instanceof CallableFailFastCondition) {
CallableFailFastCondition callableFailFastCondition = (CallableFailFastCondition) failFastCondition;
Boolean terminalFailureReached = callableFailFastCondition.getFailFastCondition().call();
if (terminalFailureReached != null && terminalFailureReached) {
String failureReason = failFastCondition.getFailFastFailureReason();
String failureReason = callableFailFastCondition.getFailFastFailureReason();
throw new TerminalFailureException(failureReason);
}
} else if (failFastCondition instanceof FailFastAssertion) {
FailFastAssertion failFastAssertion = (FailFastAssertion) failFastCondition;
try {
failFastAssertion.getFailFastAssertion().run();
} catch (Throwable e) {
String failFastFailureReason = failFastAssertion.getFailFastFailureReason();
throw new TerminalFailureException(failFastFailureReason == null ? e.getMessage() : failFastFailureReason, e);
}
}
}

Expand Down
58 changes: 52 additions & 6 deletions awaitility/src/main/java/org/awaitility/core/ConditionFactory.java
Expand Up @@ -17,6 +17,8 @@

import org.awaitility.constraint.AtMostWaitConstraint;
import org.awaitility.constraint.WaitConstraint;
import org.awaitility.core.FailFastCondition.CallableFailFastCondition;
import org.awaitility.core.FailFastCondition.CallableFailFastCondition.FailFastAssertion;
import org.awaitility.pollinterval.FixedPollInterval;
import org.awaitility.pollinterval.PollInterval;
import org.hamcrest.Description;
Expand All @@ -38,8 +40,7 @@
import static org.hamcrest.Matchers.is;

/**
* A factory for creating {@link org.awaitility.core.Condition} objects. It's not recommended to
* instantiate this class directly.
* A factory for creating {@link org.awaitility.core.Condition} objects. It's not recommended instantiating this class directly.
*/
public class ConditionFactory {

Expand Down Expand Up @@ -582,15 +583,15 @@ public ConditionFactory failFast(Callable<Boolean> failFastCondition) {
throw new IllegalArgumentException("failFastCondition cannot be null");
}
return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
exceptionsIgnorer, conditionEvaluationListener, executorLifecycle, new FailFastCondition(null, failFastCondition));
exceptionsIgnorer, conditionEvaluationListener, executorLifecycle, new CallableFailFastCondition(null, failFastCondition));
}

/**
* If the supplied Callable <i>ever</i> returns false, it indicates our condition will <i>never</i> be true, and if so fail the system immediately.
* Throws a {@link TerminalFailureException} if fail fast condition evaluates to <code>true</code>.
*
* @param failFastCondition The terminal failure condition
* @param failFastFailureReason A descriptive reason why the fail fast condition has failed, will be included in the {@link TerminalFailureException} thrown if <code>failFastCondition</code> evaluates to <code>true</code>.
* @param failFastFailureReason A descriptive reason why the fail fast condition has failed, will be included in the {@link TerminalFailureException} thrown if <code>failFastCondition</code> evaluates to <code>true</code>.
* @param failFastCondition The terminal failure condition
* @return the condition factory
*/
public ConditionFactory failFast(String failFastFailureReason, Callable<Boolean> failFastCondition) {
Expand All @@ -601,7 +602,52 @@ public ConditionFactory failFast(String failFastFailureReason, Callable<Boolean>
}

return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
exceptionsIgnorer, conditionEvaluationListener, executorLifecycle, new FailFastCondition(failFastFailureReason, failFastCondition));
exceptionsIgnorer, conditionEvaluationListener, executorLifecycle, new CallableFailFastCondition(failFastFailureReason, failFastCondition));
}

/**
* If the supplied <code>failFastAssertion</code> <i>ever</i> returns throws an exception, it indicates our condition will <i>never</i> be true, and if so fail the system immediately.
* This allows you to use a more descriptive error message of why the fail-fast condition failed by doing e.g.:
*
* <pre>
* Workflow workflow = ..
* await()
* .atMost(1, MINUTES)
* .failFast(() -> assertThat(workflow.get("phase")).describedAs("Workflow failed. Last known state:\n" + workflow.toPrettyString()).isNotEqualTo("Failed");
* .untilAsserted(...);
* </pre>
*
* @param failFastAssertion The terminal failure assertion
* @return the condition factory
* @see #failFast(String, Callable)
*/
public ConditionFactory failFast(final ThrowingRunnable failFastAssertion) {
return failFast(null, failFastAssertion);
}

/**
* If the supplied <code>failFastAssertion</code> <i>ever</i> returns throws an exception, it indicates our condition will <i>never</i> be true, and if so fail the system immediately.
* This allows you to use a more descriptive error message of why the fail-fast condition failed by doing e.g.:
*
* <pre>
* Workflow workflow = ..
* await()
* .atMost(1, MINUTES)
* .failFast("workflow failed", () -> assertThat(workflow.get("phase")).describedAs("Workflow failed. Last known state:\n" + workflow.toPrettyString()).isNotEqualTo("Failed");
* .untilAsserted(...);
* </pre>
*
* @param failFastFailureReason A descriptive reason why the fail fast condition has failed, will be included in the {@link TerminalFailureException} thrown if <code>failFastAssertion</code> throws an exception.
* @param failFastAssertion The terminal failure assertion
* @return the condition factory
* @see #failFast(String, Callable)
*/
public ConditionFactory failFast(String failFastFailureReason, final ThrowingRunnable failFastAssertion) {
if (failFastAssertion == null) {
throw new IllegalArgumentException("failFastAssertion cannot be null");
}
return new ConditionFactory(alias, timeoutConstraint, pollInterval, pollDelay, catchUncaughtExceptions,
exceptionsIgnorer, conditionEvaluationListener, executorLifecycle, new FailFastAssertion(failFastFailureReason, failFastAssertion));
}

/**
Expand Down
74 changes: 61 additions & 13 deletions awaitility/src/main/java/org/awaitility/core/FailFastCondition.java
@@ -1,23 +1,71 @@
/*
* Copyright 2022 the original author or authors.
*
* 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 org.awaitility.core;

import java.util.concurrent.Callable;

public class FailFastCondition {
private static final String DEFAULT_FAILURE_REASON = "Fail fast condition triggered";

private final Callable<Boolean> failFastCondition;
private final String failFastFailureReason;
// Simulate sealed classes

public FailFastCondition(String failFastFailureReason, Callable<Boolean> failFastCondition) {
this.failFastCondition = failFastCondition;
this.failFastFailureReason = failFastFailureReason == null ? DEFAULT_FAILURE_REASON : failFastFailureReason;
public abstract class FailFastCondition {
private FailFastCondition() {
}

public Callable<Boolean> getFailFastCondition() {
return this.failFastCondition;
}
public abstract String getFailFastFailureReason();

public static final class CallableFailFastCondition extends FailFastCondition {
private static final String DEFAULT_FAILURE_REASON = "Fail fast condition triggered";

private final Callable<Boolean> failFastCondition;
private final String failFastFailureReason;

public CallableFailFastCondition(String failFastFailureReason, Callable<Boolean> failFastCondition) {
this.failFastCondition = failFastCondition;
this.failFastFailureReason = failFastFailureReason == null ? DEFAULT_FAILURE_REASON : failFastFailureReason;
}

public Callable<Boolean> getFailFastCondition() {
return this.failFastCondition;
}

@Override
public String getFailFastFailureReason() {
return this.failFastFailureReason;
}

public static final class FailFastAssertion extends FailFastCondition {
private final String failFastFailureReason;
private final ThrowingRunnable failFastAssertion;

public FailFastAssertion(String failFastFailureReason, ThrowingRunnable failFastAssertion) {
if (failFastAssertion == null) {
throw new IllegalArgumentException("failFastAssertion cannot be null");
}
this.failFastFailureReason = failFastFailureReason;
this.failFastAssertion = failFastAssertion;
}

public ThrowingRunnable getFailFastAssertion() {
return failFastAssertion;
}

public String getFailFastFailureReason() {
return this.failFastFailureReason;
@Override
public String getFailFastFailureReason() {
return failFastFailureReason;
}
}
}
}
@@ -1,3 +1,19 @@
/*
* Copyright 2022 the original author or authors.
*
* 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 org.awaitility.core;

/**
Expand All @@ -8,4 +24,8 @@ public class TerminalFailureException extends RuntimeException {
public TerminalFailureException(final String message) {
super(message);
}

public TerminalFailureException(final String message, Throwable cause) {
super(message, cause);
}
}

0 comments on commit fc45eb7

Please sign in to comment.