Fix Get On Session errors
[integration/test.git] / csit / libraries / WaitUtils.robot
index e1f29b178a2368bf9605f5176a0cfd94dd3d3236..3fb5c7b01e87dbfa77b0e59ce13ce417a0a4f795 100644 (file)
@@ -1,37 +1,40 @@
 *** Settings ***
-Documentation     Robot keyword library (Resource) with several Keywords for monitoring and waiting.
+Documentation       Robot keyword library (Resource) with several Keywords for monitoring and waiting.
 ...
-...               Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+...                 Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
 ...
-...               This program and the accompanying materials are made available under the
-...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
-...               and is available at http://www.eclipse.org/legal/epl-v10.html
+...                 This program and the accompanying materials are made available under the
+...                 terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...                 and is available at http://www.eclipse.org/legal/epl-v10.html
 ...
 ...
-...               BuiltIn.Wait_Until_Keyword_Succeeds is useful in avoiding unnecessary sleeps.
-...               But several usage cases need slightly different logic, here are Keywords for that.
+...                 BuiltIn.Wait_Until_Keyword_Succeeds has two possible results: Fast pass or fail on timeout.
+...                 Generally, keywords in this Resource also allow for some kind of fast failure condition.
+...                 This usually requires more than a single keyword to run inside the iteration loop.
+...                 This library uses ScalarClosures for plugging in specific (multiple) Keywords.
 ...
-...               This library uses ScalarClosures for plugging in specific Keywords.
-...               Storing private state in suite variables is easy, but it can lead to hard-to-debug issues,
-...               so this library tries to support explicit state passing.
-...               Unfortunately, failing limits type of message to return,
-...               so implementation of some Keywords looks quite convoluted.
+...                 Storing private state in suite variables is easy, but it can lead to hard-to-debug issues,
+...                 so this library tries to support explicit state passing.
+...                 Unfortunately, failing limits type of message to return,
+...                 so implementation of some Keywords looks quite convoluted.
 ...
-...               Particular closures are to be given by caller:
-...               Stateless Assertor: Take no arguments. Return comment or Fail with message.
-...               Stateful Assertor: Take single ${state} argument. Return new state and comment, or Fail with message.
-...               (Stateless) Getter: Take no argument. Return single scalar data, or Fail with message.
-...               Stateless Validator: Take single ${data} argument. Return comment, or Fail with message.
-...               (Unsafe) Stateful Validator: Take ${state} and ${data} arguments. Return new state and comment, or Fail with message.
-...               Safe Stateful Validator: Take ${state} and ${data} arguments. Return new state, validation status and comment/message.
-...               TODO: Create a dummy closure for each type to be used as default value?
+...                 Particular closures are to be given by caller:
+...                 Stateless Assertor: Take no arguments. Return comment or Fail with message.
+...                 Stateful Assertor: Take single ${state} argument. Return new state and comment, or Fail with message.
+...                 (Stateless) Getter: Take no argument. Return single scalar data, or Fail with message.
+...                 Stateless Validator: Take single ${data} argument. Return comment, or Fail with message.
+...                 (Unsafe) Stateful Validator: Take ${state} and ${data} arguments. Return new state and comment, or Fail with message.
+...                 Safe Stateful Validator: Take ${state} and ${data} arguments. Return new state, validation status and comment/message.
+...                 TODO: Create a dummy closure for each type to be used as default value?
 ...
-...               TODO: Figure out a way to merge this with FaitForFailure.robot
-...               TODO: Add Keywords that are Safe (return state, success and message)
-...               so that callers do not need to refresh state explicitly.
-Library           DateTime
-Library           String
-Resource          ${CURDIR}/ScalarClosures.robot
+...                 TODO: Figure out a way to merge this with WaitForFailure.robot
+...                 TODO: Add Keywords that are Safe (return state, success and message)
+...                 so that callers do not need to refresh state explicitly.
+
+Library             DateTime
+Library             String
+Resource            ${CURDIR}/ScalarClosures.robot
+
 
 *** Keywords ***
 WU_Setup
@@ -39,183 +42,352 @@ WU_Setup
     ScalarClosures.SC_Setup
 
 Limiting_Stability_Safe_Stateful_Validator_As_Keyword
-    [Arguments]    ${old_state}    ${data}    ${valid_minimum}=-1
     [Documentation]    Report failure if minimum not reached or data value changed from last time. Useful to become validator.
+    [Arguments]    ${old_state}    ${data}    ${valid_minimum}=-1
     ${new_state} =    BuiltIn.Set_Variable    ${data}
-    BuiltIn.Return_From_Keyword_If    ${data} < ${valid_minimum}    ${new_state}    FAIL    Minimum not reached.
-    BuiltIn.Return_From_Keyword_If    ${data} != ${old_state}    ${new_state}    FAIL    Data value has changed.
-    [Return]    ${new_state}    PASS    Validated stable: ${data}
+    IF    ${data} < ${valid_minimum}
+        RETURN    ${new_state}    FAIL    Minimum not reached.
+    END
+    IF    ${data} != ${old_state}
+        RETURN    ${new_state}    FAIL    Data value has changed.
+    END
+    RETURN    ${new_state}    PASS    Validated stable: ${data}
 
 Create_Limiting_Stability_Safe_Stateful_Validator_From_Value_To_Overcome
-    [Arguments]    ${maximum_invalid}=-1
     [Documentation]    Helper function to use if maximum invalid value (instead of minimum valid) is known.
+    [Arguments]    ${maximum_invalid}=-1
     ${valid_minimum} =    BuiltIn.Evaluate    str(int(${maximum_invalid}) + 1)
-    ${validator} =    ScalarClosures.Closure_From_Keyword_And_Arguments    WaitUtils.Limiting_Stability_Safe_Stateful_Validator_As_Keyword    state_holder    data_holder    valid_minimum=${valid_minimum}
-    [Return]    ${validator}
+    ${validator} =    ScalarClosures.Closure_From_Keyword_And_Arguments
+    ...    WaitUtils.Limiting_Stability_Safe_Stateful_Validator_As_Keyword
+    ...    state_holder
+    ...    data_holder
+    ...    valid_minimum=${valid_minimum}
+    RETURN    ${validator}
+
+Excluding_Stability_Safe_Stateful_Validator_As_Keyword
+    [Documentation]    Report failure if got the excluded value or if data value changed from last time. Useful to become validator.
+    [Arguments]    ${old_state}    ${data}    ${excluded_value}=-1
+    ${new_state} =    BuiltIn.Set_Variable    ${data}
+    IF    ${data} == ${excluded_value}
+        RETURN    ${new_state}    FAIL    Got the excluded value.
+    END
+    IF    ${data} != ${old_state}
+        RETURN    ${new_state}    FAIL    Data value has changed.
+    END
+    RETURN    ${new_state}    PASS    Validated stable: ${data}
 
 WaitUtils__Check_Sanity_And_Compute_Derived_Times
-    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1
     [Documentation]    Common checks for argument values. Return times in seconds and deadline date implied by timeout time.
+    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1
     # Sanity check ${count}.
-    BuiltIn.Run_Keyword_If    int(${count}) < 1    BuiltIn.Fail    \${count} is ${count} and not at least 1.
+    IF    int(${count}) < 1
+        BuiltIn.Fail    \${count} is ${count} and not at least 1.
+    END
     # Sanity check ${period}.
     ${period_in_seconds} =    DateTime.Convert_Time    ${period}    result_format=number
-    BuiltIn.Run_Keyword_If    ${period_in_seconds} <= 0.0    BuiltIn.Fail    \${period} ${period} has to be positive.
+    IF    ${period_in_seconds} <= 0.0
+        BuiltIn.Fail    \${period} ${period} has to be positive.
+    END
     # Figure out deadline.
     ${date_now} =    DateTime.Get_Current_Date
     ${timeout_in_seconds} =    DateTime.Convert_Time    ${timeout}    result_format=number
     # In the following line, arguments have to be in order which is opposite to what name suggests.
     ${date_deadline} =    DateTime.Add_Time_To_Date    ${date_now}    ${timeout_in_seconds}
-    [Return]    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline}
+    RETURN    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline}
 
 WaitUtils__Is_Deadline_Reachable
-    [Arguments]    ${date_deadline}=0    ${period_in_seconds}=1    ${sleeps_left}=1    ${message}=No attempt made.
     [Documentation]    Compute time to be wasted in sleeps, compare to deadline. Fail with message when needed.
+    [Arguments]    ${date_deadline}=0    ${period_in_seconds}=1    ${sleeps_left}=1    ${message}=No attempt made.
     # FIXME: Sensible default for deadline?
     ${date_now} =    DateTime.Get_Current_Date
     ${time_deadline} =    DateTime.Subtract_Date_From_Date    ${date_deadline}    ${date_now}    result_format=number
     ${time_minimal} =    BuiltIn.Evaluate    int(${sleeps_left}) * ${period_in_seconds}
-    BuiltIn.Run_Keyword_If    ${time_minimal} >= ${time_deadline}    BuiltIn.Fail    Not possible to succeed within the deadline. ${message}
+    IF    ${time_minimal} >= ${time_deadline}
+        BuiltIn.Fail    Not possible to succeed within the deadline. ${message}
+    END
+
+Wait_For_Getter_Failure_Or_Stateless_Validator_Pass
+    [Documentation]    Repeatedly run getter and plug its output to validator. If both pass, return validator message.
+    ...    If getter fails, fail. If validator fails, repeat in WUKS fashion (fail when timeout is exceeded).
+    ...    FIXME: Cover this keyword in WaitUtilTest.robot
+    [Arguments]    ${timeout}=60s    ${period}=1s    ${getter}=${ScalarClosures__fail}    ${stateless_validator}=${ScalarClosures__identity}
+    ${timeout_in_seconds}
+    ...    ${period_in_seconds}
+    ...    ${date_deadline} =
+    ...    WaitUtils__Check_Sanity_And_Compute_Derived_Times
+    ...    timeout=${timeout}
+    ...    period=${period}
+    ${iterations} =    BuiltIn.Evaluate    ${timeout_in_seconds} / ${period_in_seconds}
+    FOR    ${i}    IN RANGE    ${iterations}
+        ${data} =    ScalarClosures.Run_Keyword_And_Collect_Garbage    ScalarClosures.Run_Closure_As_Is    ${getter}
+        ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error
+        ...    ScalarClosures.Run_Keyword_And_Collect_Garbage
+        ...    ScalarClosures.Run_Closure_After_Replacing_First_Argument
+        ...    ${stateless_validator}
+        ...    ${data}
+        IF    "${status}" == "PASS"    RETURN    ${message}
+        WaitUtils__Is_Deadline_Reachable
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    message=Last validator message: ${message}
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
+    BuiltIn.Fail    Logic error, we should have returned before.
 
 Stateless_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline
+    [Documentation]    Pass only if \${assertor} passes ${count} times in a row with ${period_in_seconds} between attempts; less standard arguments.
     [Arguments]    ${date_deadline}=0    ${period_in_seconds}=1    ${count}=1    ${assertor}=${ScalarClosures__fail}
-    [Documentation]    Pass only if ${assertor} passes ${count} times in a row with ${period_in_seconds} between attempts; less standard arguments.
     ${result} =    BuiltIn.Set_Variable    No result yet.
     # Do we have enough time to succeed?
     ${sleeps} =    BuiltIn.Evaluate    ${count} - 1
-    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    sleeps_left=${sleeps}    message=Last result: ${result}
+    WaitUtils__Is_Deadline_Reachable
+    ...    date_deadline=${date_deadline}
+    ...    period_in_seconds=${period_in_seconds}
+    ...    sleeps_left=${sleeps}
+    ...    message=Last result: ${result}
     # Entering the main loop.
-    : FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1    # If count is 3, for will go through 2, 1, and 0.
-    \    # Run the assertor and collect the garbage.
-    \    ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage    ScalarClosures.Run_Closure_As_Is    ${assertor}
-    \    # We have not failed yet. Was this the final try?
-    \    BuiltIn.Return_From_Keyword_If    ${sleeps_left} <= 0    ${result}
-    \    # Is there enough time left?
-    \    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    sleeps_left=${sleeps_left}    message=Last result: ${result}
-    \    # We will do next try, byt we have to sleep before.
-    \    BuiltIn.Sleep    ${period_in_seconds} s
+    FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1    # If count is 3, for will go through 2, 1, and 0.
+        # Run the assertor and collect the garbage.
+        ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage
+        ...    ScalarClosures.Run_Closure_As_Is
+        ...    ${assertor}
+        # We have not failed yet. Was this the final try?
+        IF    ${sleeps_left} <= 0    RETURN    ${result}
+        # Is there enough time left?
+        WaitUtils__Is_Deadline_Reachable
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    sleeps_left=${sleeps_left}
+        ...    message=Last result: ${result}
+        # We will do next try, byt we have to sleep before.
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
     BuiltIn.Fail    Logic error, we should have returned before.
 
 Stateless_Assert_Closure_Has_To_Succeed_Consecutively
+    [Documentation]    Pass only if \${assertor} passes ${count} times in a row with ${period} between attempts; standard arguments.
     [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${assertor}=${ScalarClosures__fail}
-    [Documentation]    Pass only if ${assertor} passes ${count} times in a row with ${period} between attempts; standard arguments.
     # TODO: Put default values into variables for users to override at pybot invocation?
-    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline} =    WaitUtils__Check_Sanity_And_Compute_Derived_Times    timeout=${timeout}    period=${period}    count=${count}
-    ${result} =    Stateless_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    count=${count}    assertor=${assertor}
-    [Return]    ${result}
+    ${timeout_in_seconds}
+    ...    ${period_in_seconds}
+    ...    ${date_deadline} =
+    ...    WaitUtils__Check_Sanity_And_Compute_Derived_Times
+    ...    timeout=${timeout}
+    ...    period=${period}
+    ...    count=${count}
+    ${result} =    Stateless_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline
+    ...    date_deadline=${date_deadline}
+    ...    period_in_seconds=${period_in_seconds}
+    ...    count=${count}
+    ...    assertor=${assertor}
+    RETURN    ${result}
 
 Stateful_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline
+    [Documentation]    Pass only if $\{assertor} passes ${count} times in a row with ${period} between attempts. Keep assertor state in local variable. Less standard arguments.
     [Arguments]    ${date_deadline}=0    ${period_in_seconds}=1    ${count}=1    ${assertor}=${ScalarClosures__fail}    ${initial_state}=${None}
-    [Documentation]    Pass only if ${assertor} passes ${count} times in a row with ${period} between attempts. Keep assertor state in local variable. Less standard arguments.
     # TODO: Put default values into variables for users to override.
     ${result} =    BuiltIn.Set_Variable    No result yet.
     ${state} =    BuiltIn.Set_Variable    ${initial_state}
     # Do we have enough time to succeed?
     ${sleeps} =    BuiltIn.Evaluate    ${count} - 1
-    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    sleeps_left=${sleeps}    message=Last result: ${result}
+    WaitUtils__Is_Deadline_Reachable
+    ...    date_deadline=${date_deadline}
+    ...    period_in_seconds=${period_in_seconds}
+    ...    sleeps_left=${sleeps}
+    ...    message=Last result: ${result}
     # Entering the main loop.
-    : FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1
-    \    ${state}    ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage    ScalarClosures.Run_Closure_After_Replacing_First_Argument    ${assertor}    ${state}
-    \    # We have not failed yet. Was this the final try?
-    \    BuiltIn.Return_From_Keyword_If    ${sleeps_left} <= 0    ${result}
-    \    # Is there enough time left?
-    \    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    sleeps_left=${sleeps_left}    message=Last result: ${result}
-    \    # We will do next try, byt we have to sleep before.
-    \    BuiltIn.Sleep    ${period_in_seconds} s
+    FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1
+        ${state}    ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage
+        ...    ScalarClosures.Run_Closure_After_Replacing_First_Argument
+        ...    ${assertor}
+        ...    ${state}
+        # We have not failed yet. Was this the final try?
+        IF    ${sleeps_left} <= 0    RETURN    ${result}
+        # Is there enough time left?
+        WaitUtils__Is_Deadline_Reachable
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    sleeps_left=${sleeps_left}
+        ...    message=Last result: ${result}
+        # We will do next try, byt we have to sleep before.
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
     BuiltIn.Fail    Logic error, we should have returned before.
 
 Stateful_Assert_Closure_Has_To_Succeed_Consecutively
+    [Documentation]    Pass only if \${assertor} passes ${count} times in a row with ${period} between attempts. Keep assertor state in local variable. Standard arguments.
     [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${assertor}=${ScalarClosures__fail}    ${initial_state}=${NONE}
-    [Documentation]    Pass only if ${assertor} passes ${count} times in a row with ${period} between attempts. Keep assertor state in local variable. Standard arguments.
     # TODO: Put default values into variables for users to override.
-    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline} =    WaitUtils__Check_Sanity_And_Compute_Derived_Times    timeout=${timeout}    period=${period}    count=${count}
-    ${result} =    Stateful_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    count=${count}    assertor=${assertor}    initial_state=${initial_state}
-    [Return]    ${result}
+    ${timeout_in_seconds}
+    ...    ${period_in_seconds}
+    ...    ${date_deadline} =
+    ...    WaitUtils__Check_Sanity_And_Compute_Derived_Times
+    ...    timeout=${timeout}
+    ...    period=${period}
+    ...    count=${count}
+    ${result} =    Stateful_Assert_Closure_Has_To_Succeed_Consecutively_By_Deadline
+    ...    date_deadline=${date_deadline}
+    ...    period_in_seconds=${period_in_seconds}
+    ...    count=${count}
+    ...    assertor=${assertor}
+    ...    initial_state=${initial_state}
+    RETURN    ${result}
 
 Getter_And_Safe_Stateful_Validator_Have_To_Succeed_Consecutively_By_Deadline
+    [Documentation]    Pass only if consecutively ${count} times in a row with ${period} between attempts: \${getter} creates data and \${safe_validator} passes. Validator updates its state even if it reports failure. Always return validator state, status and message.
     [Arguments]    ${date_deadline}=0    ${period_in_seconds}=1    ${count}=1    ${getter}=${ScalarClosures__fail}    ${safe_validator}=${ScalarClosures__fail}    ${initial_state}=${NONE}
-    [Documentation]    Pass only if consecutively ${count} times in a row with ${period} between attempts: ${getter} creates data and ${safe_validator} passes. Validator updates its state even if it reports failure. Always return validator state, status and message.
     ${result} =    BuiltIn.Set_Variable    No result yet.
     ${state} =    BuiltIn.Set_Variable    ${initial_state}
     # Do we have enough time to succeed?
     ${sleeps} =    BuiltIn.Evaluate    ${count} - 1
-    ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}    sleeps_left=${sleeps}
+    ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error
+    ...    WaitUtils__Is_Deadline_Reachable
+    ...    date_deadline=${date_deadline}
+    ...    period_in_seconds=${period_in_seconds}
+    ...    sleeps_left=${sleeps}
     ...    message=Last result: ${result}
-    BuiltIn.Return_From_Keyword_If    '''${status}''' != '''PASS'''    ${state}    ${status}    ${message}
+    IF    '''${status}''' != '''PASS'''
+        RETURN    ${state}    ${status}    ${message}
+    END
     # Entering the main loop.
-    : FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1
-    \    # Getter may fail, but this Keyword should return state, so we need RKAIE.
-    \    ${status}    ${data} =    BuiltIn.Run_Keyword_And_Ignore_Error    ScalarClosures.Run_Keyword_And_Collect_Garbage    ScalarClosures.Run_Closure_As_Is    ${getter}
-    \    BuiltIn.Return_From_Keyword_If    '''${status}''' != '''PASS'''    ${state}    ${status}    Getter failed: ${data}
-    \    # TODO: Do we want to check time here?
-    \    ${state}    ${status}    ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage    ScalarClosures.Run_Closure_After_Replacing_First_Two_Arguments    ${safe_validator}
-    \    ...    ${state}    ${data}
-    \    # Validator may have reported failure.
-    \    BuiltIn.Return_From_Keyword_If    '''${status}''' != '''PASS'''    ${state}    ${status}    Validator failed: ${result}
-    \    # Was this the final try?
-    \    BuiltIn.Return_From_Keyword_If    ${sleeps_left} <= 0    ${state}    ${status}    ${result}
-    \    # Is there enough time left?
-    \    ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error    WaitUtils__Is_Deadline_Reachable    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}
-    \    ...    sleeps_left=${sleeps_left}    message=Last result: ${result}
-    \    BuiltIn.Return_From_Keyword_If    '''${status}''' != '''PASS'''    ${state}    ${status}    ${message}
-    \    # We will do next try, byt we have to sleep before.
-    \    BuiltIn.Sleep    ${period_in_seconds} s
+    FOR    ${sleeps_left}    IN RANGE    ${count}-1    -1    -1
+        # Getter may fail, but this Keyword should return state, so we need RKAIE.
+        ${status}    ${data} =    BuiltIn.Run_Keyword_And_Ignore_Error
+        ...    ScalarClosures.Run_Keyword_And_Collect_Garbage
+        ...    ScalarClosures.Run_Closure_As_Is
+        ...    ${getter}
+        IF    '''${status}''' != '''PASS'''
+            RETURN    ${state}    ${status}    Getter failed: ${data}
+        END
+        # Is there enough time left?
+        ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error
+        ...    WaitUtils__Is_Deadline_Reachable
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    sleeps_left=${sleeps_left}
+        ...    message=Last result: ${result}
+        IF    '''${status}''' != '''PASS'''
+            RETURN    ${state}    ${status}    ${message}
+        END
+        ${state}    ${status}    ${result} =    ScalarClosures.Run_Keyword_And_Collect_Garbage
+        ...    ScalarClosures.Run_Closure_After_Replacing_First_Two_Arguments
+        ...    ${safe_validator}
+        ...    ${state}
+        ...    ${data}
+        # Validator may have reported failure.
+        IF    '''${status}''' != '''PASS'''
+            RETURN    ${state}    ${status}    Validator failed: ${result}
+        END
+        # Was this the final try?
+        IF    ${sleeps_left} <= 0
+            RETURN    ${state}    ${status}    ${result}
+        END
+        # Is there enough time left?
+        ${status}    ${message} =    BuiltIn.Run_Keyword_And_Ignore_Error
+        ...    WaitUtils__Is_Deadline_Reachable
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    sleeps_left=${sleeps_left}
+        ...    message=Last result: ${result}
+        IF    '''${status}''' != '''PASS'''
+            RETURN    ${state}    ${status}    ${message}
+        END
+        # We will do next try, byt we have to sleep before.
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
     BuiltIn.Fail    Logic error, we should have returned before.
 
 Propagate_Fail_If_Message_Starts_With_Prefix
-    [Arguments]    ${message}=${EMPTY}    ${prefix}=magic
     [Documentation]    Helper keyword to distinguish escalable failures by their prefix. If it is escalable, Fail without changing the message; otherwise Return comment.
+    [Arguments]    ${message}=${EMPTY}    ${prefix}=magic
     # TODO: Move to a more appropriate Resource.
     # Empty message cannot fit prefix.
     ${status}    ${result} =    BuiltIn.Run_Keyword_And_Ignore_Error    BuiltIn.Should_Be_Empty    ${message}
-    BuiltIn.Return_From_Keyword_If    '${status}' == 'PASS'    Got empty message.
+    IF    '${status}' == 'PASS'    RETURN    Got empty message.
     # Is there anything except the prefix?
-    @{message_chunks}=    String.Split_String    ${message}    ${prefix}
+    @{message_chunks} =    String.Split_String    ${message}    ${prefix}
     # If there is something at the first chunk, the prefix was not at start.
     ${status}    ${result} =    BuiltIn.Run_Keyword_And_Ignore_Error    BuiltIn.Should_Be_Empty    ${message_chunks[0]}
-    BuiltIn.Return_From_Keyword_If    '${status}' != 'PASS'    ${message} does not start with ${prefix}
+    IF    '${status}' != 'PASS'
+        RETURN    ${message} does not start with ${prefix}
+    END
     # We got the fail to propagate
     BuiltIn.Fail    ${message}
 
 Wait_For_Getter_And_Safe_Stateful_Validator_Consecutive_Success
-    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${getter}=${ScalarClosures__fail}    ${safe_validator}=${ScalarClosures__fail}    ${initial_state}=${NONE}
     [Documentation]    Analogue of Wait Until Keyword Succeeds, but it passes state of validator around. Calls GASSVHTSCBD to verify data is "stable".
+    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${getter}=${ScalarClosures__fail}    ${safe_validator}=${ScalarClosures__fail}    ${initial_state}=${NONE}
     # FIXME: Document that Safe Stateful Validator has to return state, status and message (and never fail)
-    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline} =    WaitUtils__Check_Sanity_And_Compute_Derived_Times    timeout=${timeout}    period=${period}    count=${count}
-    # Maximum number of tries. TODO: Move to separate Keyword?
-    ${maximum_tries} =    BuiltIn.Evaluate    math.ceil(${timeout_in_seconds} / ${period_in_seconds})    modules=math
+    ${timeout_in_seconds}
+    ...    ${period_in_seconds}
+    ...    ${date_deadline} =
+    ...    WaitUtils__Check_Sanity_And_Compute_Derived_Times
+    ...    timeout=${timeout}
+    ...    period=${period}
+    ...    count=${count}
+    # Maximum number of sleeps. TODO: Move to separate Keyword?
+    ${maximum_sleeps} =    BuiltIn.Evaluate
+    ...    math.ceil(${timeout_in_seconds} / ${period_in_seconds}) + 1
+    ...    modules=math
     ${result} =    BuiltIn.Set_Variable    No result yet.
     ${state} =    BuiltIn.Set_Variable    ${initial_state}
     # The loop for failures.
-    : FOR    ${try}    IN RANGE    1    ${maximum_tries}
-    \    ${state}    ${status}    ${result} =    Getter_And_Safe_Stateful_Validator_Have_To_Succeed_Consecutively_By_Deadline    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}
-    \    ...    count=${count}    getter=${getter}    safe_validator=${safe_validator}    initial_state=${state}
-    \    # Have we passed?
-    \    BuiltIn.Return_From_Keyword_If    '''${status}''' == '''PASS'''    ${result}
-    \    # Are we out of time?
-    \    Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Not possible to succeed within the deadline.
-    \    # We will do next try, but we have to sleep before.
-    \    BuiltIn.Sleep    ${period_in_seconds} s
+    FOR    ${try}    IN RANGE    1    ${maximum_sleeps}+2    # If maximum_sleeps is 2, for will go through 1, 2, and 3.
+        ${state}
+        ...    ${status}
+        ...    ${result} =
+        ...    Getter_And_Safe_Stateful_Validator_Have_To_Succeed_Consecutively_By_Deadline
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    count=${count}
+        ...    getter=${getter}
+        ...    safe_validator=${safe_validator}
+        ...    initial_state=${state}
+        # Have we passed?
+        IF    '''${status}''' == '''PASS'''    RETURN    ${result}
+        # Are we out of time?
+        Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Not possible to succeed within the deadline.
+        # We will do next try, but we have to sleep before.
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
     BuiltIn.Fail    Logic error, we should have returned before.
 
 Wait_For_Getter_Error_Or_Safe_Stateful_Validator_Consecutive_Success
-    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${getter}=${ScalarClosures__fail}    ${safe_validator}=${ScalarClosures__fail}    ${initial_state}=${NONE}
     [Documentation]    Analogue of Wait Until Keyword Succeeds, but it passes state of validator around and exits early on getter failure. Calls GASSVHTSCBD to verify data is "stable".
-    ${timeout_in_seconds}    ${period_in_seconds}    ${date_deadline} =    WaitUtils__Check_Sanity_And_Compute_Derived_Times    timeout=${timeout}    period=${period}    count=${count}
-    # Maximum number of tries. TODO: Move to separate Keyword or add into CSACDT?
-    ${maximum_tries} =    BuiltIn.Evaluate    math.ceil(${timeout_in_seconds} / ${period_in_seconds})    modules=math
+    [Arguments]    ${timeout}=60s    ${period}=1s    ${count}=1    ${getter}=${ScalarClosures__fail}    ${safe_validator}=${ScalarClosures__fail}    ${initial_state}=${NONE}
+    # If this ever fails, we want to know the exact inputs passed to it.
+    ${tmp} =    BuiltIn.Evaluate    int(${count})
+    BuiltIn.Log    count=${tmp}
+    ${timeout_in_seconds}
+    ...    ${period_in_seconds}
+    ...    ${date_deadline} =
+    ...    WaitUtils__Check_Sanity_And_Compute_Derived_Times
+    ...    timeout=${timeout}
+    ...    period=${period}
+    ...    count=${count}
+    # Maximum number of sleeps. TODO: Move to separate Keyword or add into CSACDT?
+    ${maximum_sleeps} =    BuiltIn.Evaluate    math.ceil(${timeout_in_seconds} / ${period_in_seconds})    modules=math
     ${result} =    BuiltIn.Set_Variable    No result yet.
     ${state} =    BuiltIn.Set_Variable    ${initial_state}
     # The loop for failures.
-    : FOR    ${try}    IN RANGE    1    ${maximum_tries}
-    \    ${state}    ${status}    ${result} =    Getter_And_Safe_Stateful_Validator_Have_To_Succeed_Consecutively_By_Deadline    date_deadline=${date_deadline}    period_in_seconds=${period_in_seconds}
-    \    ...    count=${count}    getter=${getter}    safe_validator=${safe_validator}    initial_state=${state}
-    \    # Have we passed?
-    \    BuiltIn.Return_From_Keyword_If    '''${status}''' == '''PASS'''    ${result}
-    \    # Are we out of time? Look at ${result}.
-    \    Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Not possible to succeed within the deadline.
-    \    # Now check for getter error, by analysing ${result}.
-    \    Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Getter failed
-    \    # We can do the next try, byt we have to sleep before.
-    \    BuiltIn.Sleep    ${period_in_seconds} s
+    FOR    ${try}    IN RANGE    1    ${maximum_sleeps}+2    # If maximum_sleeps is 2, for will go through 1, 2, and 3.
+        ${state}
+        ...    ${status}
+        ...    ${result} =
+        ...    Getter_And_Safe_Stateful_Validator_Have_To_Succeed_Consecutively_By_Deadline
+        ...    date_deadline=${date_deadline}
+        ...    period_in_seconds=${period_in_seconds}
+        ...    count=${count}
+        ...    getter=${getter}
+        ...    safe_validator=${safe_validator}
+        ...    initial_state=${state}
+        # Have we passed?
+        IF    '''${status}''' == '''PASS'''    RETURN    ${result}
+        # Are we out of time? Look at ${result}.
+        Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Not possible to succeed within the deadline.
+        # Now check for getter error, by analysing ${result}.
+        Propagate_Fail_If_Message_Starts_With_Prefix    ${result}    Getter failed
+        # We can do the next try, byt we have to sleep before.
+        BuiltIn.Sleep    ${period_in_seconds} s
+    END
     BuiltIn.Fail    Logic error, we should have returned before.