Skip to content

Commit

Permalink
Add onTimeout support for polling conditions (#1853)
Browse files Browse the repository at this point in the history
fixes #1355
  • Loading branch information
questras authored Jan 24, 2024
1 parent ffb6b39 commit a4d4b88
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class PollingConditions {
private double initialDelay = 0;
private double delay = 0.1;
private double factor = 1.0;
private Closure<String> timeoutMessage = null;

/**
* Returns the timeout (in seconds) until which the conditions have to be satisfied.
Expand Down Expand Up @@ -128,6 +129,21 @@ public void setFactor(double factor) {
this.factor = factor;
}

/**
* Sets the closure that is evaluated when a timeout is reached.
* <p>
* The closure can use a {@link Throwable} as an input parameter,
* which is thrown by the test conditions when a timeout is reached. The result of this
* closure is added to the {@link SpockTimeoutError} message. Calling it with null resets the timeout message.
*
* @param timeoutMessage the closure that is evaluated when a timeout is reached
* @since 2.4
*/
@Beta
public void onTimeout(Closure<String> timeoutMessage) {
this.timeoutMessage = timeoutMessage;
}

/**
* Repeatedly evaluates the specified conditions until they are satisfied or the timeout has elapsed.
*
Expand Down Expand Up @@ -182,6 +198,9 @@ public void within(double seconds, Closure<?> conditions) throws InterruptedExce
}

String msg = String.format("Condition not satisfied after %1.2f seconds and %d attempts", elapsedTime / 1000d, attempts);
if (timeoutMessage != null) {
msg = String.format("%s: %s", msg, GroovyRuntimeUtil.invokeClosure(timeoutMessage, testException));
}
throw new SpockTimeoutError(seconds, msg, testException);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class PollingConditionsSpec extends Specification {
volatile int num = 0
volatile String str = null

private static def noArgClosure = { "test" }
private static def throwableArgClosure = { Throwable err -> err.getClass().simpleName }

def "defaults"() {
expect:
with(conditions) {
Expand Down Expand Up @@ -168,4 +171,47 @@ class PollingConditionsSpec extends Specification {
then: "there will be no second one"
thrown SpockTimeoutError
}

def "correctly creates timeout error message"() {
given:
PollingConditions conditions = new PollingConditions()
conditions.onTimeout(onTimeoutClosure)

when:
conditions.eventually {
1 == 0
}

then:
def e = thrown(SpockTimeoutError)
e.message ==~ /Condition not satisfied after \d+(\.\d+)? seconds and \d+ attempts/ + expectedMessageSuffix

where:
onTimeoutClosure || expectedMessageSuffix
null || ""
noArgClosure || ": test"
throwableArgClosure || ": ConditionNotSatisfiedError"
}

def "correctly creates timeout error message when onTimeout called multiple times"() {
given:
PollingConditions conditions = new PollingConditions()
conditions.onTimeout(onTimeoutClosure)
conditions.onTimeout(secondOnTimeoutClosure)

when:
conditions.eventually {
1 == 0
}

then:
def e = thrown(SpockTimeoutError)
e.message ==~ /Condition not satisfied after \d+(\.\d+)? seconds and \d+ attempts/ + expectedMessageSuffix

where:
onTimeoutClosure | secondOnTimeoutClosure || expectedMessageSuffix
noArgClosure | null || ""
null | noArgClosure || ": test"
throwableArgClosure | noArgClosure || ": test"
}
}

0 comments on commit a4d4b88

Please sign in to comment.