Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onTimeout support for polling conditions #1853

Merged
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"
}
}
Loading