Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmake/registry.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,15 @@ function(add_docs_from_registry)
endforeach()
endif()

# symbol_needs: reads the Doxygen document's deploy/xml/<id>/ the same
# way testmodule does, so the same stage-2 edge.
string(JSON _zdocs_symbol_dox_src GET "${_zdocs_entry}" "symbol_needs_doxygen_source")
if(NOT _zdocs_symbol_dox_src STREQUAL "")
foreach(_zdocs_builder ${_zdocs_builders})
add_dependencies(${_zdocs_id}-${_zdocs_builder} ${_zdocs_symbol_dox_src})
endforeach()
endif()

string(JSON _zdocs_testmodule_spec GET "${_zdocs_entry}" "testmodule_spec")
if(NOT _zdocs_testmodule_spec STREQUAL "")
add_dependencies(${_zdocs_id}-index ${_zdocs_testmodule_spec}-index)
Expand Down
24 changes: 24 additions & 0 deletions doc/api/python/extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ directives.
.. automodule:: test_module
:members:

``symbol_needs``
------------------

The ``.. symbolneeds::`` directive.

.. automodule:: symbol_needs
:members:

``input_tracking``
--------------------

Re-reads a document when an input from outside the source tree changes.

.. automodule:: input_tracking
:members:

``needs_fields``
------------------

Which optional need fields a consumer declared.

.. automodule:: needs_fields
:members:

``xref_builder``
------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ methodology vocabulary; the fields hanging off them are the engine's own data
model. The distinction is visible in a generated needs table, where literal
field names sit beside a mapped link name.

Two fields were later made roles too: a test result's ``depends_met`` and
``skip_class`` (``testreport_need_fields``). They are verdicts a project's
review filters and reports by, not a record of what twister wrote, so a
consumer names them like the types and links.

Consequences
------------

Expand Down
2 changes: 2 additions & 0 deletions doc/manual/howto/render-test-specifications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Everything the directives emit — three need types (case/procedure/result),
three link types, and up to nine custom fields — must be declared in
``needs_config.toml`` (``ZDOCS_NEEDS_CONFIG``), or sphinx-needs rejects the
need with an ``Unknown option``/``Unknown need type`` warning per occurrence.
The ``depends_on`` field (Kconfig conditions from ``@kconfig_depends``) is the
exception: the directives set it only when you declare it.
Renaming the three roles away from the engine defaults, if you want project
vocabulary rather than ``test_case``/``verifies``/etc., is a matching pair of
``conf.py`` dicts — see :doc:`../reference/directives-and-roles`.
Expand Down
9 changes: 9 additions & 0 deletions doc/manual/reference/consumer-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ layout, not toggled from outside it.
per-document (every document in a set is handed an import of every other's
needs, so they all have to agree on what a need type means).

It must declare every need type, link and field the engine's directives
emit under the names you map their roles to: the test directives' three
types, three links and custom fields, and — for a document with a
``symbol_needs:`` block — the ``implementation`` type and ``satisfies``
link (:doc:`directives-and-roles`). Declaring the ``satisfies`` link in
this project-wide file is also what lets a requirements document show the
link's incoming side. One field is optional: ``depends_on`` (the
``@kconfig_depends`` conditions) is set only if declared here.

This variable is real and load-bearing — every sample and fixture in this
repository that uses sphinx-needs sets it — but it is not mentioned
alongside the others in ``cmake/zdocs.cmake``'s own "Consumer configuration"
Expand Down
149 changes: 146 additions & 3 deletions doc/manual/reference/directives-and-roles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ What a document author writes in RST. Every directive here is loaded for
every document by :external+zdocs-api:py:func:`zdocs_conf.configure`, whether
or not the document uses it — an extension list that varied by document would
make the two build stages' configuration differ and invalidate the shared
doctree cache. The ``testmodule``/``testreport``/``twisterinfo`` directives are
the one exception: they load only for a document whose registry entry carries
a ``testmodule:`` block (:doc:`registry-schema`).
doctree cache. The ``testmodule``/``testreport``/``twisterinfo`` directives and
``symbolneeds`` are the exceptions: they load only for a document whose
registry entry carries a ``testmodule:`` or ``symbol_needs:`` block
(:doc:`registry-schema`).

``.. doc_control::``
--------------------
Expand Down Expand Up @@ -103,6 +104,36 @@ locate that module's ``testcase.yaml`` for the rendered scenario table. Every
``ZTEST``/``ZTEST_SUITE``/... in the named group and its inner suite/procedure
groups becomes one need each — nothing is written by hand per test case.

A test case need's ``suite`` field is its inner suite group's name, so name
that group after the ``ZTEST_SUITE``: ``testreport`` finds the test case for a
twister result by the pair (suite, function). Two test modules that declare the
same ztest suite (Zephyr's workq ``user_work`` and ``work_queue`` both declare
``workqueue_api``) cannot share one group, though, because then both module
pages render every test in it and the need ids collide. So give each module its
own group, and set a qualifier in the ``conf.py`` of the document that holds
the ``testmodule`` directives, after the ``configure()`` call:

.. code-block:: python

testmodule_suite_qualifier = "__"

.. code-block:: c

/** @defgroup kernel_workq_user_work_module__workqueue_api workqueue_api ZTest suite
* @ingroup kernel_workq_user_work_module */

The suite is the part of the group name after the qualifier's **last**
occurrence (here ``workqueue_api``). A group name without the qualifier is used
whole, and so is every name when the value is unset (the default, ``""``). The
qualifier splits only the Doxygen group name the suite is derived from. The
(suite, function) key a result is correlated by does not change, and it now
sees the real suite name, which is the point. The fallback id of a test case
without ``@testid``, ``testspec-<group>-<function>``, keeps the whole group
name: two modules may have a function of the same name in the same suite, and
their ids must not collide. A result of such a function is still ambiguous by
(suite, function), and ``testreport`` skips it with a warning, as it does for
any pair that two modules document.

.. code-block:: rst

.. testreport:: twister_report.xml
Expand Down Expand Up @@ -145,6 +176,35 @@ result, which takes its status from them and lists the values that did not
pass (:doc:`../explanation/testmodule-and-twister`). No need type or field is
added for this; the values render in the need's body.

Each result also says whether its build met the test case's ``depends_on``
(the ``@kconfig_depends`` conditions below), and each skipped result why it was
skipped. Both are read from the run, not from the spec alone:

``depends_met``
``yes`` or ``no``: the case's conditions, all of which must hold, evaluated
against the ``.config`` twister kept for that build
(``<platform>/<toolchain>/<test path>/<scenario>/zephyr/.config`` under the
report's directory). ``CONFIG_X`` and ``defined(CONFIG_X)`` are true when
the symbol has a value (``=y``, a number, a string; not ``is not set``);
``!``, ``&&``, ``||`` and parentheses combine them. ``n/a`` when the case
has no condition, the build's ``.config`` is not there, or a condition uses
anything else (another macro, ``IS_ENABLED()``, a comparison): its value is
not known from ``.config``, so none is guessed, and the build warns once per
case and condition. A result that passed with ``no`` ran although its
condition was false.
``skip_class``
On skipped results only. ``build-only``: twister built the test but did not
run it. ``platform``: a memory region overflowed, or the platform was
filtered out. ``config``: ztest skipped it and ``depends_met`` is ``no``.
``unexplained``: anything else.

Both are set only where your ``needs_config.toml`` declares them (string
fields), under names you may choose, like the need types and links:

.. code-block:: python

testreport_need_fields = {"depends_met": "depends_met", "skip_class": "skip_class"} # defaults

Both directives **soft-fail** to a short "not found" paragraph
when their input is absent, rather than failing the build — a documentation
build outrunning its test run is a normal pipeline state. ``testmodule`` does
Expand Down Expand Up @@ -181,18 +241,101 @@ the parser matches on, and must be spelled exactly):
ALIASES += "testid{1}=\xrefitem testids \"Test ID\" \"Test IDs\" \1"
ALIASES += "reqref{1}=\xrefitem reqrefs \"Requirement\" \"Requirements\" \1"

A third alias is optional. ``@kconfig_depends{<condition>}`` records the
Kconfig condition a test case (or, with ``symbolneeds``, an API symbol) is
built under; the key ``kconfig_depends`` is what the parser matches, the titles
are yours and the first one labels the rendered line:

.. code-block:: text

ALIASES += "kconfig_depends{1}=\xrefitem kconfig_depends \"Depends on\" \"Kconfig dependencies\" \1"

Every condition is kept verbatim (``(CONFIG_A && !CONFIG_B) || CONFIG_C``;
write a comma as ``\,``), once, in source order. It renders in the need's body
("Depends on: ``CONFIG_ASSERT``") on ``testmodule`` and ``symbolneeds`` needs,
and fills the optional field ``depends_on`` — the conditions joined with
``"; "`` — if, and only if, your ``needs_config.toml`` declares it. Declare it
as a string field:

.. code-block:: toml

[needs.fields.depends_on]
description = "Kconfig conditions the need depends on"
nullable = true
[needs.fields.depends_on.schema]
type = "string"

Left undeclared, no need gets the field and nothing warns. Declared as an
``array``, it works only while no condition contains ``;``, ``|`` or ``,``,
where sphinx-needs splits an array value; a need with such a condition gets no
field and a warning instead.

.. code-block:: c

/**
* @reqref{DUTY_001}
* @see acme_widget_init()
* @kconfig_depends{CONFIG_WIDGET_PROBE}
* @testid{WIDGET-PROBE-001}
*/
ZTEST(widget_probe_suite, test_widget_reports_initial_value)
{
...
}

``.. symbolneeds::``
--------------------

One need per API symbol (function, macro, ...) carrying a Doxygen 1.16
``\satisfies <UID>``, linked to the requirement needs those UIDs name. Loads
only for a document whose registry entry has a ``symbol_needs:`` block
(:doc:`registry-schema`), which also names the Doxygen document whose XML is
read. Source: :external+zdocs-api:py:mod:`symbol_needs`.

.. code-block:: rst

.. symbolneeds::

.. symbolneeds:: queue_apis

Without an argument, every annotated symbol in the Doxygen project is
emitted, in sections headed by the group (or file) that documents it. With a
Doxygen group name, only that group's symbols, with no heading. Each symbol is
emitted once. A symbol without ``\satisfies`` gets no need.

Each need is titled with the symbol name and has the id
``<TYPE>-<symbol>``, where ``<TYPE>`` is the need type's name in upper case
(``IMPL-k_queue_init``). Its body gives the kind, the brief, a link to the
symbol's Doxygen page and the file it is declared in; no custom field is
needed for them. The need type and link are engine roles, named by the
consumer like the test directives' (ADR-0009):

.. code-block:: python

symbolneeds_need_types = {"implementation": "impl"} # the defaults
symbolneeds_need_links = {"satisfies": "satisfies"}

Declare both in ``needs_config.toml``; the link's ``incoming`` name is what a
requirement's page shows:

.. code-block:: toml

[[needs.types]]
directive = "impl"
title = "Implementation"
prefix = "IMPL_"

[needs.links.satisfies]
outgoing = "satisfies"
incoming = "satisfied by"

A ``\satisfies`` naming a UID that no requirement need has is reported like a
dangling ``verifies``: sphinx-needs warns "unknown outgoing link" in the
stage-2 build (the XML cannot tell, since Doxygen writes a
``requirement_<UID>`` refid for any UID), and Doxygen's own "Reference to
unknown requirement" warning fails the Doxygen document through
``ZDOCS_DOXYGEN_WARN_FAIL_PATTERNS`` (:doc:`consumer-contract`).

Doxylink prefixes
-----------------

Expand Down
30 changes: 30 additions & 0 deletions doc/manual/reference/registry-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,36 @@ Each entry in ``documents:``, keyed by its id:
``crossref: false`` on either side removes the entry, as for any tag file.
Needs Doxygen 1.16 or newer.

``symbol_needs``
Opt-in sub-block; loads the ``symbolneeds`` directive
(:doc:`directives-and-roles`) into this document, which emits one need per
API symbol that carries a Doxygen ``\satisfies``, linked to the
requirements it names. One key, required:

.. code-block:: yaml

api-documentation:
kind: sphinx
builders: [html]
needs:
source: json # so peers import the symbol needs
symbol_needs:
doxygen_source: dox-safety-api # a kind: doxygen document

``doxygen_source`` names the ``kind: doxygen`` document whose XML
(``deploy/xml/<id>/``, so the top-level ``doxygen_xml: true`` is needed)
holds the symbols; every stage-2 builder of this document waits for it, as
for ``testmodule.doxygen_source``. Its HTML is what each need links to.
Only a ``kind: sphinx`` document may carry the block; a missing or unknown
key, a non-mapping value, or a ``doxygen_source`` that is not an existing
``kind: doxygen`` document is a configure-time error.

Add ``needs: {source: json}`` as shown: it is what makes every peer import
the symbol needs, so a requirement authored in another document lists its
implementing symbols ("satisfied by", or whatever incoming name your
``needs_config.toml`` gives the link) beside its verifying test cases.
Without the block, nothing changes: the extension is not loaded.

``testmodule``
Opt-in sub-block (see :doc:`../explanation/testmodule-and-twister` and
:doc:`directives-and-roles`); its presence is the sole trigger that loads
Expand Down
Loading