Chapter 10Lesson 05250–340 min

Checkpoint Lab — IF/ELSE, FOR, WHILE, TRY/EXCEPT, BREAK, CONTINUE, and Control Flow

Implement a synthetic state machine using IF/FOR/WHILE/TRY, enforce hard limits, inject transient and permanent failures, prove only the intended failure is recovered, and refactor nested logic into named user keywords.

Checkpoint labState machineHard limitsFailure injectionRefactoring

Learning objectives

  • Model a small synthetic state machine with explicit branch, iteration, recovery, and terminal states.
  • Enforce both iteration and semantic limits so the lab cannot run indefinitely.
  • Inject transient and permanent failures and prove only the transient failure is recovered.
  • Preserve branch/loop/error evidence in the Robot output directory.
  • Refactor an over-nested implementation into user keywords with clear contracts and final status propagation.

Current compatibility baseline. Verified 2026-08-31: Robot Framework 7.4.2 is the current stable release; 7.5b1 is a pre-release and is not required in this chapter. Native IF/ELSE arrived in Robot Framework 4.0; WHILE, TRY/EXCEPT, BREAK, CONTINUE, and inline IF are available in modern Robot Framework 5.0+ syntax. Robot Framework 7.4.2 gives WHILE a default 10,000-iteration limit; production examples here set a smaller explicit limit.

1. Checkpoint charter and safety boundary

The entire state machine is synthetic. It writes only a trace file under the selected Robot output directory. It does not call networks, browsers, databases, SSH, containers, external processes, or production systems. Failure injection uses Fail with fake messages.

Success criterion. The checkpoint is not “everything passes.” One diagnostic run must fail with a permanent error. The goal is to prove that transient recovery is narrow and permanent failure remains visible.

2. State model and predictions

Synthetic state transitions
stateDiagram-v2
[*] --> queued
queued --> warming
warming --> warming: transient failure/retry
warming --> ready: successful observation
warming --> failed: permanent failure
ready --> [*]
failed --> [*]
Input mode Expected path Expected final result
normal queued → warming → ready PASS
transient-once queued → warming → transient caught → ready PASS
permanent queued → warming → permanent failure FAIL; permanent message preserved
never-ready queued → warming until hard limit FAIL with convergence/limit message

3. Refactored implementation

*** Settings ***
Library    OperatingSystem

*** Variables ***
${TRACE}    ${OUTPUT DIR}/state-machine-trace.txt

*** Test Cases ***
Normal State Machine
    ${final}=    Run Synthetic State Machine    normal    max_steps=6
    Should Be Equal    ${final}    ready

Transient Failure Is Recovered
    ${final}=    Run Synthetic State Machine    transient-once    max_steps=6
    Should Be Equal    ${final}    ready

Permanent Failure Is Not Recovered
    [Tags]    expected-diagnostic-fail
    Run Synthetic State Machine    permanent    max_steps=6

Never Ready Is Bounded
    [Tags]    expected-diagnostic-fail
    Run Synthetic State Machine    never-ready    max_steps=4

*** Keywords ***
Run Synthetic State Machine
    [Arguments]    ${mode}    ${max_steps}=6
    Create File    ${TRACE}    mode=${mode}\n
    VAR    ${state}    queued
    VAR    ${step}     ${0}

    WHILE    $state != "ready"    limit=${max_steps}
        ${step}=    Evaluate    $step + 1
        Trace    step=${step};state=${state}

        ${state}=    Advance One State    ${state}    ${mode}    ${step}

        IF    $state == "failed"
            Fail    permanent: synthetic state machine failed at step ${step}
        END
    END

    Trace    final=${state};steps=${step}
    RETURN    ${state}

Advance One State
    [Arguments]    ${state}    ${mode}    ${step}
    IF    $state == "queued"
        RETURN    warming
    END

    IF    $state != "warming"
        Fail    permanent: unknown state '${state}'
    END

    TRY
        ${next}=    Observe Synthetic Warming State    ${mode}    ${step}
    EXCEPT    transient:*    type=GLOB    AS    ${error}
        Trace    caught=${error}
        RETURN    warming
    END
    RETURN    ${next}

Observe Synthetic Warming State
    [Arguments]    ${mode}    ${step}
    IF    $mode == "transient-once" and $step == 2
        Fail    transient: synthetic dependency warming
    ELSE IF    $mode == "permanent" and $step == 2
        Fail    permanent: synthetic configuration invalid
    ELSE IF    $mode == "never-ready"
        RETURN    warming
    ELSE
        RETURN    ready
    END

Trace
    [Arguments]    ${message}
    Append To File    ${TRACE}    ${message}\n    ...    encoding=UTF-8

4. Why the permanent failure is not caught

Advance One State catches only messages matching transient:*. The permanent failure from Observe Synthetic Warming State does not match and therefore propagates through the user keyword to the test. This is the central checkpoint proof.

5. Preflight

python --version
robot --version
robot --dryrun --outputdir evidence/dryrun suites/state_machine.robot

6. Run PASS and diagnostic FAIL slices separately

# PASS paths
robot --exclude expected-diagnostic-fail --outputdir evidence/pass suites/state_machine.robot

# Permanent failure only
robot --test "Permanent Failure Is Not Recovered" --outputdir evidence/permanent suites/state_machine.robot

# Never-ready limit failure only
robot --test "Never Ready Is Bounded" --outputdir evidence/limit suites/state_machine.robot

The first command should PASS. The latter two should exit non-zero. Preserve all three output directories.

7. Verify normal and transient traces

Run Trace evidence Expected
normal step=1 queued; step=2 warming; final=ready No caught= line
transient-once caught=transient:... then another warming iteration Final ready and PASS

8. Verify only intended failure is recovered

For Permanent Failure Is Not Recovered, inspect log.html and output.xml. The error must contain permanent: synthetic configuration invalid. There must be no later final=ready trace. For Never Ready Is Bounded, the final failure must identify the WHILE limit rather than an infinite hang.

9. Refactoring exercise: recognize excessive nesting

Before the refactored implementation above, imagine all state transitions, failure matching, and trace calls were embedded directly in one test case. That would make the test describe implementation mechanics instead of the requirement. The checkpoint refactor creates three contracts: Run Synthetic State Machine, Advance One State, and Observe Synthetic Warming State. Each name explains one responsibility and each failure path remains visible in the log hierarchy.

10. Add FOR/BREAK/CONTINUE evidence

Add a separate helper that scans candidate modes before starting the machine. Skip disabled with CONTINUE and stop on the first supported mode with BREAK. Keep those statements in the FOR body:

Select First Supported Mode
    [Arguments]    @{modes}
    VAR    ${selected}    ${None}
    FOR    ${mode}    IN    @{modes}
        IF    $mode == "disabled"    CONTINUE
        IF    $mode in ["normal", "transient-once", "permanent", "never-ready"]
            VAR    ${selected}    ${mode}
            BREAK
        END
    END
    IF    $selected is None
        Fail    No supported mode supplied
    END
    RETURN    ${selected}

11. Required evidence packet

  • Robot and Python version output.
  • Exact PASS, permanent-failure, and limit-failure commands.
  • state-machine-trace.txt for the PASS/transient cases.
  • output.xml, log.html, and report.html for all three run classes.
  • Permanent failure message showing it was not caught.
  • WHILE-limit failure showing bounded non-convergence.
  • Before/after note explaining why nested test logic was extracted into named keywords.
  • Trace or log evidence for the FOR/BREAK/CONTINUE extension.

12. Production control-flow convention

Concern Convention
IF/ELSE One readable policy decision; extract when nesting obscures intent
FOR Finite known collection; BREAK/CONTINUE only in active loop body
WHILE Explicit small count/time budget tied to the domain
TRY/EXCEPT Catch only expected recoverable failure messages; preserve unmatched failures
Expressions Static authored expression + $variable data for arbitrary strings/objects
Retries Evidence per attempt; permanent failures exit immediately
Complexity Move algorithmic logic to Python library; Robot stays orchestration/specification

13. Knowledge check

What proves the transient handler is not hiding permanent defects?

Why are the diagnostic failure tests run separately?

What is the purpose of both max_steps and a WHILE limit?

Why is the refactored test more maintainable than one giant state-machine test body?

What concept follows in Chapter 11?

14. Checkpoint complete

You can now build bounded native control flow that remains an executable specification: branches are explicit, loops have hard budgets, loop control stays in the active loop body, expression data is handled safely, and recovery is narrow enough that permanent failures still fail. Chapter 11 builds on this by separating data variation from procedural branching.

Next lesson

Data-Driven Testing, Templates, Embedded Arguments, and Variants: Core Concepts and Mental Model

Continue with Data-Driven Testing, Templates, Embedded Arguments, and Variants: Core Concepts and Mental Model. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Further reading

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.