Test scenarios from the sources
Once a test is implemented, its scenario lives in the
TEST_STEP() macros of the .c file. The
scripts/scenario/scenario.py tool reads it back from there:
it parses the source with libclang, so unlike a text scan it
knows which condition, loop, or step group encloses every step.
The listing serves reviewers, documentation generation, and any
tool that wants the scenario without running the test. Scenarios
written before the code exists are covered by
Test scenarios in markdown.
Listing the steps
scenario.py steps <test.c> emits the test’s section in the
markdown dialect: summary, objective, and parameters recovered
from the doxygen header comment, then every
TEST_STEP()/TEST_SUBSTEP() (and the
TEST_STEP_PUSH/NEXT/POP stack) as the dialect step
list. The if/else/loop/switch constructs enclosing a
step become notes under it. The section is built to round-trip:
pasted into a package.md it passes the drift check as is,
which is how an existing suite bootstraps onto the markdown
workflow. This needs the optional libclang pip package and a
compiled compile_commands.json for the suite - found
automatically by walking up from the source for a build/
directory, or given explicitly with --compile-db.
A trimmed fragment and its section:
TEST_STEP("Load the driver");
if (if_status == IF_UP)
{
TEST_SUBSTEP("Bring the interface up");
}
for (i = 0; i < iters; i++)
{
TEST_SUBSTEP("Unload and reload the driver");
}
TEST_STEP("Check the driver is loaded");
## driver_unload: Driver unload stress test
Check that the driver survives an unload loop.
Parameters:
- `iters`: How many times to reload
- `if_status`: Interface state during the loop
Steps:
1. Load the driver.
- Bring the interface up.
Only when `if_status == IF_UP`.
- Unload and reload the driver.
For each iteration (`i = 0; i < iters; i++`).
2. Check the driver is loaded.
--flat prints the raw listing instead, one line per step:
STEP or SUBSTEP, a tab, the enclosing conditions in the
order they nest, and the step text - handy for grepping:
$ scenario.py steps ts/driver/driver_unload.c --flat
STEP Load the driver
SUBSTEP [if (if_status == IF_UP)] Bring the interface up
SUBSTEP [for (i = 0; i < iters; i++)] Unload and reload the driver
STEP Check the driver is loaded
Evaluating parameters
--param NAME=VALUE (repeatable) binds a test parameter - one
read via a TEST_GET_*_PARAM() call - and evaluates every
condition it appears in.
How a value binds:
a plain number binds as is (any C base);
a bool parameter binds through
TRUE/FALSE, any case;an enum parameter, declared as
TEST_GET_ENUM_PARAM(name, MAP), resolves through its mapping macro:--param if_status=updecidesif (if_status == IF_UP)the same as the numeric constant would, providedMAPexpands to{ "up", IF_UP }, ...pairs the tool can read;anything else leaves the parameter unbound, and its conditions stay annotated as before.
What a decided condition does to the listing:
decided true: the annotation disappears, the step stays;
decided false: the whole step is dropped; with
--flatthe dropped steps fold into a trailingN step(s) not taken with these parametersline, or are printed with aSKIPtab prefix under--show-skipped;a
forloop whose trip count the bound parameters pin down is not unrolled: it prints once, annotatedrepeats N times(dropped entirely for a zero count, unannotated for a one-shot loop).
What stays undecided on purpose:
conditions on runtime state, or on identifiers the parameters do not bind;
switchand theif (0)error-path landing pad;a loop whose repeat count the parameters do not pin down - the tool evaluates conditions, it does not interpret loop bodies.
Doxygen filter
Test suites that publish doxygen documentation can feed it through
the same extractor instead of the classic c2dox awk filter:
FILTER_PATTERNS = *.c="${TE_BASE}/scripts/c2dox_ast"
The filter keeps the test’s doxygen header comment and injects
the scenario after @par Scenario:, moving @author to the
bottom of the page (which c2dox meant to do, but lost the
line instead). The scenario is a nested list: steps
under the condition or loop that guards them (“If cond:”,
“For each iteration (…):”), substeps under their step,
TEST_STEP_PUSH groups under their heading. Step strings are
decoded properly - concatenation, escapes - and @p/@c
references stay for doxygen to resolve.
The filter needs python3 with the libclang package on
PATH at documentation build time; without them it falls back
to c2dox with a warning on stderr, so the build still
produces pages, just flat ones.
Inline parameter documentation
Test parameters are documented next to the code that reads them
with TEST_PARAM_DOC(). The macro expands to nothing; each
argument after the parameter name is one line of the description,
so a value list needs no \n escapes, and adjacent string
literals within one argument continue the same line:
TEST_PARAM_DOC(mode,
"Operation mode:",
"- fast: skip checks",
"- safe: full validation");
TEST_GET_ENUM_PARAM(mode, MODE_MAPPING_LIST);
The doxygen filter turns these strings into the @param entries
of the test page. When the header comment still carries a
hand-written @param of the same name, the inline text wins;
header-only entries survive in front of the generated ones. The
end state of a migrated test has no @param in the header at
all.
For a parameter read through an enum getter - a direct
TEST_GET_ENUM_PARAM() or a wrapper like
TEST_GET_ETHDEV_STATE() - the filter appends the allowed value
names to the description automatically, taken from the getter’s
mapping-list macro (harvested from the test source and the TAPI
headers under TE_BASE). An explicit value list in the doc
suppresses the mechanical one: write your own bullets when the
values deserve explanations.
scenario.py check enforces coverage: a parameter read with no
documentation, a doc without a matching read, a duplicated doc,
and description drift against the package.md parameter list
are findings. By default a header @param still counts as
documentation; --strict requires the inline form. The env
parameter is the one exception to staleness checking, because its
read is hidden inside TEST_START.
Parameter docs and scenario in the log
The same TEST_PARAM_DOC() text and TEST_STEP() scenario
that feed the doxygen filter also reach the run log, without
running the tool by hand. At build time
engine/builder/te_tests_info.sh calls
scripts/scenario/tests_info.py for every test source and folds
its output into the suite’s tests-info.xml alongside the
existing <objective>. When the optional libclang package is
importable the scenario is the same control-flow-annotated tree the
doxygen filter renders - loop and branch headings such as For
each iteration (...) and If `deep`: become steps one depth
above their bodies. Without libclang the tool needs only the
standard library and falls back to the flat textual extraction
(cstep), listing the step texts in source order. Either
way the texts carry no list markers: the consumer derives the
list markup from the depth that travels with every step:
<test name="parameters" page="minimal_parameters">
<objective>Test that various types of parameters are properly handled.</objective>
<param name="unit_param">Value with a decimal unit prefix:
- a plain number (no prefix)
- k, M, G, T suffixes (powers of 1000)</param>
<scenario>
<step depth="1">Getting required parameters</step>
</scenario>
</test>
This needs python3 on PATH at build time. Without it - or
if the extractor fails - the build still succeeds: it falls back
to the classic te_tests_info.awk scrape, which only ever
produced <objective>, so the <param>/<scenario>
entries are silently lost for that run. The build log says so:
tests-info: python tests-info generator unavailable or failed;
parameter descriptions and scenarios are not extracted
(install python3, e.g. apt install python3)
With python3 but no libclang, a milder note marks the flat scenarios:
tests_info: scenarios lack control-flow annotations
(pip install libclang to add them)
The Tester reads the extra fields back out of tests-info.xml
and logs them on the test’s test_start MI message, bumped to
version 2 for the addition: param_docs (parameter name to
description, only the parameters that have one) and scenario
(the ordered {"depth": ..., "text": ...} steps), both omitted
rather than emitted empty when a test has neither:
{"type":"test_start","version":2,"msg":{...,
"param_docs":{"unit_param":"Value with a decimal unit prefix:\n..."},
"scenario":[{"depth":1,"text":"Getting required parameters"}, ...],
"objective":"..."}}
Bublik meets this data in two places. The log view renders it
directly: the JSON log rgt-xml2json produces (what
rgt-proc-raw-log --json and the log bundle serve) carries a
description alongside each documented parameter’s value
and a scenario array next to objective in the same test
node, so the viewer can show them with the rest of the node’s
metadata. The plain-text and HTML renderers (rgt-xml2text,
rgt-xml2html, rgt-xml2html-multi) leave both out on
purpose, so the flat log is not cluttered with text that is
already in the source. Separately, for bublik’s own database
import, rgt-bublik-json copies param_docs and scenario
onto the test object straight from the test_start message,
again present only when the test has them.