Sphinx extensions
Reference documentation for the modules under sphinx/_extensions/: directives, roles and
builders loaded by zdocs_conf.configure(). See the companion
manual’s Directives and roles
page for how a document author uses each one.
doc_control
The .. doc_control:: directive — the controlled-document header table.
- class doc_control.DocCtrlDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
- has_content = False
May the directive have content?
- option_spec = {'approval_date': <function unchanged_required>, 'approved_by': <function unchanged_required>, 'author': <function unchanged_required>, 'based_on_template': <function unchanged>, 'classification': <function unchanged>, 'effective_date': <function unchanged>, 'owner': <function unchanged_required>, 'reviewed_by': <function unchanged_required>, 'supersedes': <function unchanged>, 'version': <function unchanged_required>}
Mapping of option names to validator functions.
- doc_control.latex_escape(text)[source]
Escape text for use in a LaTeX string.
Public because
zdocs_confneeds the same escaping for the running header and footer it builds out of consumer-supplied values (project name, copyright holder, document id). Those are the strings most likely to contain an&or an_, and a second copy of this table living in the config module is how the two would stop agreeing.
qms_ref
The .. qms_role:: directive.
latexinclude
The .. latexinclude:: directive.
The latexinclude directive — include an RST file in the PDF only.
.. latexinclude:: ../_glossary_terms.rst # relative to the AUTHORED file
.. latexinclude:: /_glossary_terms.rst # relative to the SOURCE TREE
A printed document carries no hyperlinks, so anything an HTML reader would reach by following a link has to travel with the PDF instead: a shared glossary, a terms-and-abbreviations appendix, a standards list. Inlining the same content into the HTML would just give that reader a second copy of a page they can already open, so this includes for LaTeX builders and does nothing everywhere else.
The path is relative to the including file as AUTHORED, which is not where
Sphinx parses it from. external_content copies each document’s folder into
<build>/<doc>/src and the build runs from that copy, so resolving the
argument against the parse location sends .. into <build>/<doc> — a
directory of doctrees and logs, never of authored RST. The only file worth
including this way is a SHARED one (a file inside the document would simply be
part of it), and a shared file is by definition outside the folder that gets
copied. So the wrong base directory does not degrade this directive, it breaks
every legitimate use of it.
The authored location is reconstructed from Sphinx’s own two facts: confdir
is the document’s authored directory (cmake passes it as -c), and docname
is the including file’s path within the document. Together they give the file the
author was looking at when they counted the ...
A leading ``/`` means relative to the source tree instead, exactly as it does
for Sphinx’s own include directive (“interprets absolute paths correctly,
i.e. relative to source directory”). The same spelling then works from both
directives and from any depth, which is the point: an authored-relative path
encodes how far the including file sits below the shared file, so moving a
document from doc/<id>/ to doc/<group>/<id>/ silently changes what
../ means and turns a working glossary into a build failure. A source-tree
path says where the file IS, not how far away the author happened to be.
That spelling requires the shared file to be present in the source tree, which
it is not by default – external_content copies only the document’s own
folder. A consumer that wants it adds it to external_content_contents (and
to exclude_patterns, since an include fragment is not a document). Both
spellings are supported and neither is deprecated: the authored-relative one
remains correct for a file that is deliberately NOT copied into the build.
- class latexinclude.LatexIncludeDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Include an RST file for LaTeX builders, skipping it for every other.
- has_content = False
May the directive have content?
- required_arguments = 1
Number of required directive arguments.
- optional_arguments = 0
Number of optional arguments after the required arguments.
- final_argument_whitespace = False
May the final argument contain whitespace?
- option_spec = {}
Mapping of option names to validator functions.
test_module
The .. testmodule::, .. testreport:: and .. twisterinfo::
directives.
Sphinx extension: testmodule and testreport directives (Route B — sphinx-needs).
- test_module.suite_name_from_group(group_name, qualifier='')[source]
The ztest suite name a suite group’s compoundname stands for.
With a testmodule_suite_qualifier, the part after its LAST occurrence (
kernel_workq_user_work_module__workqueue_api->workqueue_apifor"__"), so two test modules declaring the same ZTEST_SUITE can give it distinct Doxygen groups. Without a qualifier, without an occurrence of it, or with nothing after it, the whole name.
- class test_module.TestModuleDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Emit sphinx-needs test_case nodes for all ZTEST functions in a module group.
Usage:
.. testmodule:: kernel_queue_module :module: tests/kernel/queue
- required_arguments = 1
Number of required directive arguments.
- optional_arguments = 0
Number of optional arguments after the required arguments.
- has_content = False
May the directive have content?
- option_spec = {'module': <function unchanged>}
Mapping of option names to validator functions.
- class test_module.TestReportDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Emit sphinx-needs test_result nodes from a twister_report.xml.
Usage:
.. testreport:: twister_report.xml :path: tests/kernel/queue
:path:selects the runs of one test directory, as twister.json records it;:module:selects by scenario-name prefix. With both, a run must match both.- required_arguments = 1
Number of required directive arguments.
- optional_arguments = 0
Number of optional arguments after the required arguments.
- has_content = False
May the directive have content?
- option_spec = {'module': <function unchanged>, 'path': <function unchanged>}
Mapping of option names to validator functions.
- class test_module.TwisterInfoDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Emit a run-metadata block and per-platform summary table from twister.json.
Usage:
.. twisterinfo:: twister.json
- required_arguments = 1
Number of required directive arguments.
- optional_arguments = 0
Number of optional arguments after the required arguments.
- has_content = False
May the directive have content?
- option_spec = {}
Mapping of option names to validator functions.
test_coverage
The .. testcoverage:: directive. test_module loads it.
Sphinx extension: the testcoverage directive (coverage adequacy as needs).
.. testcoverage:: reads a per-test coverage run (adequacy) and emits one
adequacy need per requirement that the run can assess. Each need links to its
requirement (link role assesses) and carries the verdict. The directive
also writes a summary of the run and the distribution of the verdicts.
- test_coverage.ADEQUACY_FIELD_ROLES = ('verdict', 'evidence', 'coverage_run', 'judged_symbols', 'symbol_hits')
The adequacy field roles (testcoverage_need_fields).
- test_coverage.VERDICT_MEANING = {'broken': "Tests of the run reach the code. The requirement's own tests never do.", 'no-cov': 'The verifying tests have no coverage data in this run.', 'no-impl': 'No symbol satisfies the requirement.', 'partial': 'The own tests run some of the satisfying symbols, not all.', 'true': 'The own tests run every symbol that coverage can judge.', 'unattributed': 'No test of the run covers any body. Coverage cannot judge the link.', 'unresolved': 'No satisfying symbol maps to a body (a macro).'}
What each verdict says, for the distribution table.
- test_coverage.adequacy_need_id(run, req, prefix='ADQ')[source]
<prefix>-<run>/<req>, the id ofreq’s adequacy need inrun.
- test_coverage.build_adequacy_rst(req, res, run, need_names=None, fields=(), prefix='ADQ', layout='')[source]
RST lines for the adequacy need of
req(resfrom adequacy.assess).layout: the sphinx-needs layout of the need, if not empty.
- test_coverage.build_coverage_rst(results, run, source, impl_loc, need_names=None, fields=(), prefix='ADQ', layout='', impl_files=('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h'))[source]
RST lines for the whole directive: run summary, distribution, needs by verdict.
impl_files: the body files that the run searched, for the summary.
- class test_coverage.TestCoverageDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Emit one adequacy need per requirement that a per-test coverage run can assess.
Usage:
.. testcoverage:: :run: my-run :layout: adequacy
The optional argument is the run directory. Without it, the directive reads
coverage_output_dir(ZDOCS_COVERAGE_OUT).:run:names the run in the need ids. Without it, the name is the first tag on the run commit, or else the name of the run directory.:layout:sets the sphinx-needs layout of each need.testcoverage_impl_filessets the files that hold the bodies of the satisfying symbols.- required_arguments = 0
Number of required directive arguments.
- optional_arguments = 1
Number of optional arguments after the required arguments.
- has_content = False
May the directive have content?
- option_spec = {'layout': <function unchanged>, 'run': <function unchanged>}
Mapping of option names to validator functions.
symbol_needs
The .. symbolneeds:: directive.
Sphinx extension: the symbolneeds directive — one need per API symbol that satisfies a requirement.
A Doxygen 1.16 \satisfies <UID> on a function or macro becomes, in the XML,
a <satisfies> child of its memberdef. This directive turns each such symbol
into a need (the implementation role) linked to those requirements (the
satisfies role), so a requirement’s page shows what implements it next to
what verifies it.
Loaded only for a document whose registry entry has a symbol_needs: block,
which also names the Doxygen document whose XML is read.
- symbol_needs.symbol_needs_rst(xml_dir, html_dir, group=None, need_names=None, note_input=None, depends_field=None)[source]
RST lines for the symbol needs of
group(or the whole project).With no group, the needs are sectioned by compound (group or file), each heading taken from the compound’s title. A member is emitted once, where Doxygen defines it, however many compounds list it.
note_inputis called with every XML file read.depends_field(conditions, subject)decides whether a need gets thedepends_onfield (needs_fields.depends_field); without it, none does.
- class symbol_needs.SymbolNeedsDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]
Emit one need per API symbol carrying a Doxygen
\satisfies.Usage:
.. symbolneeds:: .. symbolneeds:: queue_apis
The optional argument is a Doxygen group name; without it every annotated symbol in the project is emitted, sectioned by group or file.
- required_arguments = 0
Number of required directive arguments.
- optional_arguments = 1
Number of optional arguments after the required arguments.
- has_content = False
May the directive have content?
- option_spec = {}
Mapping of option names to validator functions.
input_tracking
Re-reads a document when an input from outside the source tree changes.
Sphinx extension: track a document’s inputs from outside the source tree.
The testmodule, testreport, twisterinfo and symbolneeds directives read files Sphinx knows nothing about: Doxygen XML, twister’s XML/JSON, a spec’s needs.json. Sphinx re-reads a document only when a tracked input is missing or NEWER than the document’s last read. That is not enough here: twister output often arrives with an older mtime (a CI artifact or cache restored with its timestamps, a copy that preserves them), and the report then stays stale without a warning. So each input’s signature is recorded at read time, and a document is re-read whenever a signature differs, in either direction.
Loaded by the extensions that use it (app.setup_extension), which Sphinx
does once however many ask.
needs_config_state
Re-reads a document when its sphinx-needs TOML file or the needs it imports change.
Sphinx extension: re-read a document when its needs vocabulary or imports change.
A document’s need types, links and fields come from a TOML file
(needs_from_toml, set by zdocs_conf from ZDOCS_NEEDS_CONFIG).
Sphinx re-reads every document when a config value registered with rebuild
"env" changes, but sphinx-needs registers needs_types,
needs_links and needs_fields with rebuild "html", and the config
value needs_from_toml is only the file’s path. So after the file gains a
link type, an incremental build reuses the pickled needs, which have no entry
for the new link, and the first new need that uses it crashes the build:
KeyError: "Link type 'fulfills' does not exist in backlinks.". A clean
build works.
This extension registers zdocs_needs_config_digest, rebuild "env",
and sets it to the SHA-256 of the file’s content. A change to the file changes
the value, and Sphinx re-reads the document (“config changed”). The value is
the same for both build stages, so the stage caches stay valid while the file
does not change.
Re-reading is not enough when a link type is REMOVED: the pickled environment
still holds needs imported from a peer’s needs.json under the old vocabulary,
and the importing document crashes the same way once, although the peer’s
file no longer has the link. So on a change the pickled environment is also
dropped before Sphinx loads it (config-inited runs first), and the
document starts from a fresh one, as in a clean build. The digests the
environment was built with are kept beside it, in the doctree directory.
The needs that a document imports (needs_external_needs) have a
similar problem. sphinx-needs loads them again on each build, but Sphinx
writes only the pages of the documents that it read. When a peer’s
needs.json gains a need that links to a need of this document, the source of
the page with that need does not change. So Sphinx does not write the page
again, and the page does not show the new incoming link. For example, a
requirement page did not show “assessed by” after the test report gained
adequacy needs.
So the extension also registers zdocs_imported_needs_digest, rebuild
"env": a SHA-256 over the needs that each imported file gives. A change
to it also starts from a fresh environment. This also removes the needs that
a peer no longer has.
- needs_config_state.STAMP_NAME = 'zdocs-needs-config.sha256'
the digests it was built with.
- Type:
Beside the pickled environment
- needs_config_state.ENV_PICKLE = 'environment.pickle'
Sphinx’s pickled environment in the doctree directory.
- needs_config_state.needs_config_digest(confdir, from_toml)[source]
SHA-256 of the needs TOML file, or
""if none is set or it is unreadable.from_tomlis resolved againstconfdir, as sphinx-needs resolves it.
- needs_config_state.imported_needs_digest(confdir, external_needs)[source]
SHA-256 over the needs that
needs_external_needsimports, or""if none.Only
json_pathsources count. A relative path is resolved againstconfdir, as sphinx-needs resolves it. Ajson_urlsource is not read.
- needs_config_state.drop_stale_environment(doctreedir, digest)[source]
Delete the pickled environment in
doctreedirunless it was built withdigest.An environment with no recorded digest (a build dir from before this extension) counts as stale. Returns whether one was deleted; records
digesteither way.
needs_fields
Which optional need fields a consumer declared.
Optional need fields: set one only where the consumer declared it.
A need type, a link and a field are the consumer’s vocabulary
(needs_config.toml). The engine can fill some fields the consumer may not
want; setting an undeclared one makes sphinx-needs warn “Unknown option” on
every need. So an optional field is set only when the running sphinx-needs
schema has it, and how it is declared decides whether its value survives.
- needs_fields.DEPENDS_ON = 'depends_on'
The Kconfig conditions of a test case or API symbol (
@kconfig_depends{<condition>}), joined with"; ".
- needs_fields.field_type(env, name)[source]
The declared schema type of need field
name, orNoneif undeclared.
- needs_fields.depends_field(env, conditions, subject)[source]
Whether to set
depends_onforsubject’sconditions.Declare it as a string field: the value is the conditions joined with
"; ", verbatim. Anarrayfield works too, as long as no condition contains one of; | ,— sphinx-needs would splitA || BorIS_ENABLED(A, B)into pieces — so such a need gets no field and a warning instead of a silently wrong value.
xref_builder
The stage-1 xref builder for the two-stage documentation build.
Stage-1 xref builder for the two-stage documentation build.
Cross-referenced documents form a dependency cycle: each doc’s HTML needs the
indexes (objects.inv / needs.json / Doxygen tag files) of the docs it
links to. Those indexes depend only on a doc’s own content, so the build is
split in two:
stage 1 — every doc emits just its index (this builder), no HTML;
stage 2 — every doc builds HTML with all indexes present, so cross references resolve.
This builder subclasses Sphinx’s dummy builder: it reads/parses the whole
project (populating the environment) but writes no HTML. In finish() it
dumps objects.inv for intersphinx. needs.json is handled separately by
sphinx-needs itself: when needs_build_json = True its build-finished
hook writes needs.json into the same output directory regardless of the
active builder — so a sphinx-needs doc gets both artifacts from one xref build.
Stage 2 uses a SEPARATE doctree cache (-d) and parses for itself, once every
peer index exists. Sharing one cache is what this build system used to do, and it
was wrong: a role that resolves at parse time — :external+<inv>: is one — was
resolved during stage 1 against inventories that did not exist yet, and stage 2,
reusing that parse, wrote the empty result out. See the doctree comment in
zdocs/cmake/sphinx.cmake.
- class xref_builder.XrefBuilder(app: Sphinx, env: BuildEnvironment)[source]
- name = 'xref'
The builder’s name. This is the value used to select builders on the command line.
- epilog = 'Cross-reference index written to %(outdir)s.'
The message emitted upon successful build completion. This can be a printf-style template string with the following keys:
outdir,project
doxygen_parser
Doxygen XML parsing, with no Sphinx dependency of its own.
Doxygen XML parsing — no Sphinx dependency.
- class doxygen_parser.RefLinks(api: str = '', local: str = '', tags: Mapping[str, str] | None = None)[source]
Where a <ref> in Doxygen prose points, as HTML directory URLs.
A symbol Doxygen resolved through a tag file carries the tag file’s path in
external=;tagsmaps such a path to the HTML directory of the document that tag file belongs to, so each reference goes to the project that documents the symbol.apiis for a tag-file referencetagsdoes not name — the API document the test specification references.localis for a symbol documented in the parsed project itself, such as a shared test procedure. An empty string leaves that kind unlinked.- api: str
Alias for field number 0
- local: str
Alias for field number 1
- tags: Mapping[str, str] | None
Alias for field number 2
- doxygen_parser.ref_to_rst(ref: Element, links: RefLinks | None) str | None[source]
A <ref> as an RST hyperlink into the Doxygen HTML, or None if it has none.
Doxygen’s refid is
<compound>_1<anchor>for a member and the bare compound for a page or group. The caller decides what an unlinked ref becomes.
- doxygen_parser.elem_text(elem: Element | None) str[source]
Walk the element tree collecting all text nodes (.text and .tail), join them, then normalise any runs of whitespace down to single spaces.
- doxygen_parser.para_text(para: Element | None, links: RefLinks | None = None) str[source]
Extract inline text from a <para>, rendering code/ref as plain text.
With
links, a symbol reference becomes a hyperlink into the Doxygen HTML (see ref_to_rst) — the same target the see-also line links to. Without, a member reference becomes a:c:func:role, which resolves only where a C domain defines the symbol; in the test documents nothing does, so it renders as unlinked code, silently. The need title must stay plain text (it is not parsed), so a caller building one passes nolinks.
- doxygen_parser.list_to_rst_lines(listelem: Element, marker: str, links: RefLinks | None = None) list[str][source]
Convert <orderedlist> or <itemizedlist> children to RST list lines.
Each item’s paragraphs go through para_rst_lines, so a list nested inside a list item survives — mutual recursion between the two functions, matching doxygen’s own nesting (<listitem><para>text<itemizedlist>…). A blank line is what makes an indented block a nested list rather than a continuation of the item’s text, which is why the blank lines para_rst_lines returns are load-bearing and must be indented as blanks (i.e. emitted empty), never dropped.
- doxygen_parser.para_rst_lines(para: Element | None, links: RefLinks | None = None) list[str][source]
One <para> as RST lines: its own prose first, then any lists it contains.
para_text deliberately skips orderedlist/itemizedlist (and parameterlist/simplesect/xrefsect, which callers render separately), so it answers “what does this paragraph SAY” and cannot answer “what does it CONTAIN”. Reading the lists here is what stops an authored list from being dropped: doxygen puts Test steps: and its bullets in two sibling <para> elements, and rendering only the first published the label with nothing under it — a clean exit with the content silently absent.
Handles text and a list in the SAME <para> (prose, blank line, list) as well as the sibling-<para> shape, because both occur and only one of them was ever exercised. Returns no trailing blank line; joining blocks is the caller’s job.
- doxygen_parser.section_to_rst(simplesect: Element, links: RefLinks | None = None) list[str][source]
Render a <simplesect kind=”par”> (Arrange/Act/Assert) into RST lines. Emits a .. rubric:: for the title, then renders each <para> child as an ordered list, unordered list, or plain prose paragraph.
- doxygen_parser.see_to_rst(simplesect_see: Element | Iterable[Element], api_html_dir: str, testspec_html_dir: str = '', tag_dirs: Mapping[str, str] | None = None) str[source]
Render one <simplesect kind=”see”>, or all of a member, into a ‘See also:’ RST line. Each <ref> becomes a hyperlink (see ref_to_rst), a :c:func: role (unlinked member refs), or a plain code span, depending on its attributes. Text that is not in a <ref> becomes a literal: @see irq_offload() gives no <ref> when no Doxygen project documents the symbol, and it was lost. Doxygen 1.16 writes one see section for each @see line, also for lines that follow each other, so a caller gives all sections of a member (
findall), not only the first.tag_dirs: RefLinks.tags.
- doxygen_parser.extract_params(detaileddesc: Element, links: RefLinks | None = None) list[tuple[str, str]][source]
Walk all <parameterlist kind=”param”> elements in the detailed description, collect each parameter’s name(s) and prose description, and return them as (name, description) pairs. Multiple names per item are joined with ‘, ‘.
- doxygen_parser.detail_rst_lines(dd: Element | None, links: RefLinks | None = None) list[str][source]
A <detaileddescription>’s own prose as RST LINES — paragraphs and lists.
Shared by member-level (@details on a ZTEST/function) and compound-level (@details on a @defgroup) descriptions — both are the same <detaileddescription><para>…</para></detaileddescription> shape.
Returns LINES, not one string per paragraph, which is the whole point: a bullet list cannot be represented as a single line. The previous version collapsed each <para> with para_text and dropped the ones that came back empty, so an authored
Test steps:
Return success
published the label and nothing else, because doxygen emits the label and the list as two sibling <para> elements and only the first survives a text-only read. Every caller therefore indents PER LINE and preserves blank lines, exactly as it already did for body_sections.
Structural children stay excluded (parameterlist, simplesect, xrefsect): callers render params, see-also and the test-id/requirement xrefsects separately, and rendering them here would duplicate them.
- doxygen_parser.parse_memberdef(memberdef: Element, compound_id: str, testspec_html_dir: str, api_html_dir: str, tag_dirs: Mapping[str, str] | None = None) MemberInfo[source]
Extract all structured fields from a <memberdef> element — name, source location, Doxygen URL, brief description, test ID, requirement refs, status, see-also, and Arrange/Act/Assert body sections — and return them as a MemberInfo.
tag_dirs: where a tag-file reference links, by tag file (RefLinks.tags).
- doxygen_parser.requirement_uids(memberdef: Element, relation: str) list[str][source]
The requirement UIDs of Doxygen’s native
\verifiesor\satisfies.relationis the element name,verifiesorsatisfies: a child of the memberdef itself, not of its description, with the UID only in each<requirement>’s refid, in source order, each UID once.The refid is NOT proof the requirement exists: Doxygen synthesizes
requirement_<UID>from the UID string whether or not any\requirementdefines it, so a typo is byte-identical here to a real link. Doxygen’s “Reference to unknown requirement” warning tells them apart, and so does the link target’s absence among the needs, which sphinx-needs reports for the link these UIDs become.
- doxygen_parser.kconfig_depends(dd: Element | None) tuple[str, list[str]][source]
(label, conditions)of thekconfig_dependsxrefitems indd.@kconfig_depends{<condition>}is an alias for\xrefitem kconfig_depends "Depends on" "Kconfig dependencies" <condition>. Doxygen 1.16 merges adjacent commands into ONE xrefsect with a<para>per condition, while commands elsewhere in the comment get xrefsects of their own; like@testid, they can sit in the last paragraph or in the last list item, so the whole description is searched. Each condition is kept verbatim ((CONFIG_A && !CONFIG_B) || CONFIG_C), once, in source order, without the trailing space Doxygen adds.labelis the xrefitem’s title as the consumer’s alias spells it (“Depends on”), or""if there is none.
- doxygen_parser.parse_symbol(memberdef: Element, html_dir: str) SymbolInfo[source]
An API symbol (function, macro, …) as a need’s data: name, kind, the requirements it satisfies, its brief, where it is declared, and its page in the Doxygen HTML under
html_dir.The page is the one Doxygen documents the member on, which its id names:
<compound>_1<anchor>, the same shape ref_to_rst links. A member in a group is documented on the group’s page, not the header’s.
rst_builders
RST string builders, with no Sphinx dependency of their own.
RST string builders — no Sphinx dependency.
- rst_builders.slugify(s)[source]
Replace non-alphanumeric runs with ‘-’ and strip leading/trailing dashes.
- rst_builders.build_need_rst(info, suite_name, module_path='', suite_title='', need_names=None, depends_field=False, id_scope=None)[source]
Build the RST block for a single test_case need.
depends_field: set thedepends_onfield (see _depends_on_rst).id_scope: what the fallback idtestspec-<scope>-<function>is scoped by — the suite’s Doxygen group name, which differs fromsuite_nameunder a testmodule_suite_qualifier; defaults tosuite_name.
- rst_builders.build_procedure_need_rst(memberdef, proc_compound_id, proc_group_name, testspec_html_dir, api_html_dir, need_names=None, tag_dirs=None)[source]
Build a test_procedure needs item for one shared test procedure.
tag_dirs: where a tag-file reference links (doxygen_parser.RefLinks.tags).
- rst_builders.build_result_rst(r, spec_id, test_module, req_ids=None, need_names=None, fields=())[source]
Build RST block for one test_result need.
fields: the result-field roles (RESULT_FIELD_ROLES) to set, under their names fromneed_names, whererhas a value for them — the ones the consumer declared; an undeclared option warns per need.
- rst_builders.build_symbol_need_rst(info, need_names=None, depends_field=False)[source]
Build the RST block for one API symbol’s need (the
implementationrole).infois a doxygen_parser.parse_symbol result. The requirements it satisfies become thesatisfieslink, so each requirement’s page lists the symbol under the link’s incoming name, beside its verifying test cases. The kind, declaration and Doxygen page go in the body, where no field has to be declared for them.depends_field: set thedepends_onfield (see _depends_on_rst).
twister_reader
Twister output parsing, with no Sphinx dependency of its own.
Twister output parsing — no Sphinx dependency.
- twister_reader.split_case_name(name, scenario)[source]
(suite, function, instance)of twister test casenameinscenario.Twister names a case
<scenario>.<suite>.<fn>, with ztest’stest_stripped fromfn(and so fromfunctionhere). A parameterized test (ZTEST_P) reports one case per value as<scenario>.<fn>[<instantiation>/ <value>]: no suite segment (suiteis""), and the value may contain anything, dots included, so it is split off before the name is.instanceis the part in brackets, orNone.
- twister_reader.normalise_test_path(path)[source]
A testsuite path in one spelling: forward slashes, no
./, no trailing/.Twister writes
pathrelative to ZEPHYR_BASE with forward slashes; a consumer typing the same directory may add a trailing slash or a leading./, or come from a Windows checkout. None of that changes which directory it is.
- twister_reader.scenario_selected(platform, scenario, module_filter=None, exact=False, path_filter=None, suite_paths=None)[source]
Whether a (platform, scenario) run belongs to the report being built.
module_filtermatches the scenario name, as a dotted prefix (or exactly withexact).path_filtermatches the testsuite directory exactly, looked up insuite_paths(see testsuite_paths). With both, a run must satisfy both. With neither, every run is selected.The scenario prefix alone is not a module: upstream scenario names do not follow the directory layout (
kernel.timeris tests/kernel/timer/timer_api, and prefixeskernel.timer.error_casefrom timer_error_case), so only the path identifies a module’s results reliably.
- twister_reader.testsuite_paths(twister_meta)[source]
{(platform, scenario): normalised path}from a loaded twister.json.twister_report.xml carries no path, only the scenario (
classname) per platform, so the path a result came from is found here, by the same pair.
- twister_reader.testcase_statuses(twister_meta)[source]
{(platform, testcase identifier): status}from a loaded twister.json.twister.json keeps statuses the JUnit XML cannot express —
blockedin particular, which the XML reports as a failure.
- twister_reader.fold_parameterized_results(results, spec_lookup=None, twister_statuses=None)[source]
Attach each parameterized test’s value results to its aggregate result.
Twister reports a ZTEST_P function once as the aggregate
<scenario>.<suite>.<fn>(from ztest’s summary) and once per value as<scenario>.<fn>[<instantiation>/<value>](see parse_twister_results, which marks the latter withinstance). The spec has one test case for the function, so the values belong to the aggregate of the same run (platform and scenario), found by the function name.A run without an aggregate gets one, with the suite taken from the aggregates of other runs of the same scenario and function if they name exactly one, else from the spec (
spec_lookup) if exactly one case carries the function.Returns
(results, unmatched): the results without the value entries, and the function names whose values could not be attached, once each.
- class twister_reader.SpecLookup(entries=())[source]
The spec’s test cases, found by the (suite, function) of a twister result.
ZTEST function names are not unique across suites (some 300 are reused in the Zephyr test tree), so the suite is part of the key. Each entry is a dict with at least id, suite and test_function.
A result’s function has ztest’s
test_prefix stripped, while the spec records the C name as written, so both spellings are tried. When the result’s suite has no such case, the bare function name is used instead — but only if exactly one case carries it. candidates() returns every match, so an ambiguous one (several suites, or one (suite, function) pair documented in several test modules) is visible to the caller; find() returns a case only when there is exactly one.
- twister_reader.load_spec_lookup(json_path, need_names=None)[source]
Read spec needs.json; return a SpecLookup of its test cases.
need_names is the same role->name mapping rst_builders.py emitters take (testmodule_need_types/testmodule_need_links, merged by the caller) — the “case” role’s need type and the “verifies” role’s link name are both consumer-configurable (step 26), and this lookup must filter/read by whatever names the consumer’s spec needs actually carry, not the engine’s own defaults. Passing nothing preserves the original literal behaviour, which is what the unchanged unit tests pin.
- twister_reader.find_handler_log(twister_out_dir, platform, toolchain, test_path, scenario_name)[source]
Return the Path to handler.log for a (platform, scenario) run, or None.
- twister_reader.find_build_config(twister_out_dir, platform, toolchain, test_path, scenario_name)[source]
Return the Path to the Kconfig
.configof a (platform, scenario) build, or None.Twister keeps each build under the run directory find_handler_log describes, with the build’s
zephyr/.configin it. Unlike the log, the configuration is looked up only where twister puts it (the path layout, or the flat--detailed-test-idone): a.configof another scenario would answer for a build it did not come from.
- twister_reader.read_kconfig(path)[source]
The Kconfig symbols a
.configsets, as a set of names.A symbol is set when it has a value (
=y,=m, a number, a string);# CONFIG_X is not setand a symbol that is absent are not.
- exception twister_reader.UnparseableCondition[source]
A
depends_oncondition outside the grammar evaluate_condition reads.
- twister_reader.evaluate_condition(condition, symbols)[source]
Whether Kconfig
conditionholds for the setsymbols(read_kconfig).The grammar:
CONFIG_Xanddefined(CONFIG_X)(ordefined CONFIG_X) are true iff the symbol is set;!,&&,||(in C’s precedence) and parentheses combine them. Anything else — another macro,IS_ENABLED(), a comparison, a number — raises UnparseableCondition: its value in the build is not known from.configalone.
- twister_reader.depends_met(conditions, symbols)[source]
(value, unparseable)for a test case’sdepends_onin one build.conditionsare the case’s conditions, all of which must hold (the"; "ofdepends_onis an and);symbolsis the build’s set Kconfig symbols, or None when its.configwas not found. The value is"yes"or"no", or"n/a"when the case has no condition, the build has no.config, or a condition is outside evaluate_condition’s grammar — thenunparseablelists those conditions, and no value is guessed, even when another condition is false.
- twister_reader.skip_class(result, met)[source]
The class of a skipped result (None for any other), given its depends_met.
build-only: twister built the test but did not run it.platform: the platform could not take it (a memory region overflowed, or it was filtered by platform).config: ztest skipped it and the case’sdepends_onis false in the build.unexplained: anything else, including a ztest skip whose condition holds, cannot be evaluated, or is not recorded.
adequacy
Coverage adequacy, with no Sphinx dependency of its own.
Coverage adequacy: do the own tests of a requirement run the code that satisfies it.
This module has no Sphinx dependency. A verifies link and a satisfies
link are claims. A per-test coverage run (west twister --coverage-per-test)
checks them against execution. For each requirement, the module finds the
bodies of the satisfying symbols in the sources of the run commit. Then it
compares their lines with the lines that the own verifying tests covered.
The port starts from doc/_scripts/traceability_app.py by Anas (zephyr
collab-safety 0cc56a35003). Source, resolve_impl_symbols and the verdicts
(evidence, adequacy) are close to the original. The keys are the keys of
zdocs:
A requirement gets its verifying test cases through the
verifieslink of the case needs.A requirement gets its satisfying symbols through the
satisfieslink of the implementation needs (<TYPE>-<symbol>, rst_builders.symbol_need_id).A test case gets its matrix keys through the
twister.jsonof the run. Each twister case goes to its spec case by (suite, function), as a test result does. The key is built from (scenario, C function name), as twister writes it. The module does not parse keys, because scenario names are prefixes of other scenario names (kernel.lifo,kernel.lifo.usage).
Two changes from the original correct errors:
File patterns use glob rules in both modes:
**/is zero or more directories, and*stays in one directory. The original usedfnmatchon thegit ls-treeoutput. There,**/needs at least one directory, soinclude/zephyr/sys/**/*.hdid not findsys/slist.h.The run commit comes from
zephyr.shabeside the twister output first. Then it comes fromenvironment.zephyr_version.
One change from the original is a setting: the files that hold the bodies.
IMPL_PATTERNS is the set of the original and the default. A consumer gives
its own set to assess (testcoverage_impl_files in Sphinx).
The verdicts are the verdicts of the original:
trueEvery symbol that coverage can judge has a body that the own tests ran.
partialSome of these symbols have such a body, others do not.
brokenOther tests of the run reach the code. The own tests never do.
unattributedNo test of the run covers any body, so coverage cannot judge the link.
unresolvedSatisfying symbols exist, but none maps to a body (a macro).
no-implNo symbol satisfies the requirement.
no-covThe verifying tests have no coverage data in this run.
- adequacy.VERDICTS = ('broken', 'partial', 'unattributed', 'unresolved', 'no-cov', 'no-impl', 'true')
the findings first.
- Type:
In the order a report lists them
- adequacy.matrix_key(scenario, function, suite=None)[source]
The
test_matrix.jsonkey of C functionfunctioninscenario.Twister names a per-test tracefile
<scenario>.<test>. If two suites of one scenario have the same test name, it uses<scenario>.<suite>.<test>. The key is that name, with_for each character outside[A-Za-z0-9_]. Givesuitefor the second form.functionkeeps itstest_prefix.
- adequacy.IMPL_PATTERNS = ('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h')
Where the original looks for bodies. The default of
impl_files.
- adequacy.keep_prefixes(impl_files=('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h'))[source]
_KEEP and the fixed start of each pattern, up to its last
/before a wildcard.A body file must be in the matrix that load_matrix keeps, so that its lines can count as covered.
arch/**/*.caddsarch/. A pattern with a wildcard in its first part adds nothing, so that the matrix does not keep files outside the tree (../modules/...).
- adequacy.load_matrix(matrix_path, keep=('kernel/', 'include/', 'lib/', 'tests/'))[source]
(by_test, by_line)of atest_matrix.json, for the files underkeep.by_test[key] = {file: set of covered lines}by_line[file] = {line (int): [keys]}
keep: path prefixes (_KEEP, or keep_prefixes of the body files).
- adequacy.run_commit(run_dir, environment, root)[source]
The commit that the run built, as a full sha in
root, orNone.The first source is
zephyr.shabeside the twister output. The second source is the-g<hash>ofenvironment.zephyr_versionin twister.json.
- adequacy.run_name(run_dir, sha, root)[source]
The name of the run: the first tag (sorted) on its commit.
If the commit has no tag, the name is the name of the run directory. Each run of characters outside
[A-Za-z0-9_-]becomes one-.
- class adequacy.Source(root, ref=None)[source]
Reads the files of the tree at the commit of the coverage run.
Coverage line numbers are correct only for the sources of the build that made them. With
ref(the run commit, run_commit), read usesgit show <ref>:<path>. The line ranges are then correct also after the working tree changes. Withoutref, read uses the working tree underroot, and the caller warns.
- adequacy.resolve_impl_symbols(source, symbols, impl_files=('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h'))[source]
The function bodies of the satisfying symbols (best effort).
A system call has more than one body.
z_impl_<sym>is the implementation in supervisor mode.z_vrfy_<sym>is the verifier that a ZTEST_USER test reaches instead. The verifier can do the operation itself and never call the z_impl body (z_vrfy_k_thread_create does this). The function collects all bodies. A test that runs one of them runs the implementation.A definition starts at column 0 (
static [ALWAYS_INLINE] inlineis permitted), and its line does not end in;. Its body ends at the first}in column 0, in 500 lines or less. In a header, only astaticline counts: other lines are prototypes or macros. So a macro has no body. A header is a.hfile anywhere in the tree (kernel/include/too).impl_files: the glob patterns of the files to search (IMPL_PATTERNS).Returns {sym: [{“file”, “a”, “b”, “variant”}, …]}.
- class adequacy.CoverageRun(name, sha, environment, by_test, by_line, cases, unmatched)[source]
One per-test coverage run, joined to the test cases of the spec.
cases[case id] = {"statuses": [...], "keys": [matrix keys]}, for each spec case that twister ran in this run.case_of_key[key] = {case ids}: the reverse map, for the question “which tests ran this line”.unmatched: the twister cases with no unique spec case (sorted names).
- adequacy.load_coverage_run(run_dir, spec_lookup, root, name=None, impl_files=('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h'))[source]
Read a coverage run directory: twister.json, coverage/test_matrix.json, zephyr.sha.
impl_files: the body files of resolve_impl_symbols. The matrix keeps their lines (keep_prefixes).Returns
(run, inputs): the CoverageRun and the files that it reads.
- adequacy.collect_links(json_paths, need_names=None)[source]
(spec_lookup, verified_by, satisfied_by, ids)from needs.json files.spec_lookup: a SpecLookup of the test cases (type rolecase).verified_by[req] = [case ids], through theverifiesrole.satisfied_by[req] = [symbols]: the implementation needs (type roleimplementation, ids<TYPE>-<symbol>), throughsatisfies.ids: each need id in the files. A link target that is not a need gets no assessment.
If several files export one need, it counts once.
- adequacy.evidence(case_ids, run)[source]
The state of the verifying cases: untested, no-run, failing, passing or skipped.
- adequacy.adequacy(symbols, case_ids, run, impl_loc)[source]
{"verdict", "impls"}of a requirement from itssymbolsandcase_ids.implshas one entry for each symbol and body, with these keys:symandvariant.file,aandb:Nonefor a symbol with no body.own: the number of body lines that the own tests ran.any: the number of body lines that any test of the run ran.own_tests:{case id: [lines]}.other_tests:{matrix key: [lines]}, for the keys of other tests.
- adequacy.assess(run, verified_by, satisfied_by, source, ids=None, impl_files=('kernel/*.c', 'kernel/**/*.c', 'include/zephyr/kernel.h', 'include/zephyr/kernel/**/*.h', 'include/zephyr/sys/**/*.h'))[source]
The assessment of each requirement in the scope of the run:
({req: result}, impl_loc).A requirement is in the scope if twister ran at least one of its verifying cases in this run. If
idsis given, the requirement must also be a need. Each result hasverdictandimpls(adequacy),evidence,symbolsandcases.impl_files: the body files (resolve_impl_symbols).