Skip to content

feat(starlark): Add per-stage 'if' and 'retry_until' expressions - #1073

Draft
michaelboulton wants to merge 12 commits into
feat/scriptable-pipelinefrom
feat/extra-keys-gha-style
Draft

feat(starlark): Add per-stage 'if' and 'retry_until' expressions#1073
michaelboulton wants to merge 12 commits into
feat/scriptable-pipelinefrom
feat/extra-keys-gha-style

Conversation

@michaelboulton

Copy link
Copy Markdown
Member

add gha style 'if' and 'retry until' actions. reuses the same starlark code.

Writing a whole 'control_flow' script is a lot of ceremony when all you want is
"only run this stage if the last one returned something". Add two per-stage keys
which are single Starlark expressions evaluated by the same embedded
interpreter, with all test variables bound directly as globals:

  - name: second stage
    if: var_x > 2

  - name: poll until ready
    max_retries: 20
    delay_after: 1
    retry_until: response.body["status"] == "ready"

No script, no load(), and stages don't need an 'id'. Both are gated behind the
existing --tavern-experimental-starlark-pipeline flag.

'if' is the Starlark counterpart of the simpleeval 'skip' key (inverted); using
both on one stage is a schema error. It applies to normal stages only, not
'finally' stages.

'retry_until' is an extra success predicate inside the existing retry() loop, so
it reuses max_retries/delay_after as-is. It additionally gets a 'response' struct
with the same properties as the one returned by run_stage(), for which
_create_response_struct is extracted into its own module. max_retries is
required alongside it, enforced by the schema.

Note these expressions are deliberately not format-string interpolated -
variables are real Starlark values, so 'var_x > 2' works and '{var_x} > 2' does
not.

Also fixes the 'pip install tavern[starlark]' hint, which should be
tavern[scriptable].
Using 'skip' on a stage now raises a DeprecationWarning pointing at the 'if'
key, which does the same thing with the logic inverted. The 'skip' *marker*,
which skips a whole test, is unaffected.

The warning fires whenever the key is present, including 'skip: False', so that
people migrating see it regardless of the value. The docs for both the boolean
and simpleeval forms are marked deprecated and link to the 'if' documentation,
with a side by side example of the equivalent expression.
'retry_until' was being evaluated after a stage passed, making it an extra
condition on top of the response block. It should instead decide whether to keep
retrying a stage that failed - a stage which passes is finished and is never
retried, so the expression is not evaluated at all.

On failure the expression is evaluated against the response from the failed
attempt. If it is true the stage is treated as finished and the test carries on,
which is the 'continue_on_fail' behaviour originally intended; if false the
stage is retried as before.

To make the response available on the failure path, TavernException grows a
'response' attribute which wrapped_run_stage sets when a verifier raises. If the
request itself failed there is no response, so the stage is just retried.
…til'

Adds coverage for the use case in #751 - polling a long
running job until it reaches any terminal state, rather than guessing how many
retries it needs.

The integration server gets a '/job/<name>' endpoint which is in progress for
the first couple of polls and then settles on SUCCESS or FAILED.
'retry_until' can only say when to stop polling, which counts as a pass.
'fail_if' is the negative counterpart - it is evaluated after every attempt
at a stage, and if it is true the test fails immediately without any further
retries. This means polling a long running job can stop as soon as it reaches
a state it will never recover from.

#751
- Updated the condition to first check for `retry_until` before evaluating `e.response` in `_core/testhelpers.py`.
- Consolidated nested checks to improve readability.
- Separated the logic handling cases where `e.response` is `None` for better debugging and maintainability.
Comment thread docs/source/scripting.md Outdated
Comment thread tavern/_core/run.py Outdated
Comment thread tavern/_core/schema/tests.jsonschema.yaml Outdated
… 'skip'

Per-stage 'if'/'retry_until'/'fail_if' expressions are now format-string
interpolated like everywhere else in Tavern, rather than binding test
variables as Starlark globals. This makes them consistent with the
existing 'skip' key and lets them refer to variables whose names are not
valid Starlark identifiers, such as ones containing a dash. Errors quote
both the original expression and the interpolated one.

'skip' is also no longer deprecated - 'if' only works on HTTP stages, so
it is not a full replacement yet.
- Added a note in `docs/source/core_concepts/marks.md` mentioning that `skip` may be removed in the future in favor of `if`.
This reverts commit 914a02b.
'if', 'retry_until' and 'fail_if' can now be a YAML block scalar with
several statements in it, where the value of the last statement decides
the result. The helper modules can be load()ed as in a 'control_flow'
script, so a condition can use 're' to pull a group out of a response.

'run_stage' is not available - the stage the expression is attached to is
already being run - and loading anything other than @tavern_helpers.star
is an error. The 're'/'time'/'log' bindings move to a new 'builtins'
module so they can be shared with the expression path, which must not
import tavern._core.run.
…assignment

Returning the value of the last statement is behaviour of the starlark
binding rather than something the language spec promises, and a script
which ends on an assignment quietly evaluates to None instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant