This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nuttx-ntfc.git
The following commit(s) were added to refs/heads/main by this push:
new b2ad84e mypytest: support alternatives and wildcards in ntfc.yaml
requirements
b2ad84e is described below
commit b2ad84e7395e1f14481f62c1c81c91f75f98a3d9
Author: raiden00pl <[email protected]>
AuthorDate: Tue Aug 4 17:34:27 2026 +0200
mypytest: support alternatives and wildcards in ntfc.yaml requirements
Requirements only supported exact [key, value] matching, which cannot
express init configuration that differs between build modes: flat
builds use CONFIG_INIT_ENTRYPOINT, kernel builds CONFIG_INIT_FILEPATH.
Add, backward compatibly:
- [key, "*"]: any truthy value satisfies
- [[key, value], ...]: alternatives, any one satisfies
Also name the product, core and unmet requirement in the abort message.
Signed-off-by: raiden00pl <[email protected]>
Assisted-by: Claude Code
---
Documentation/ntfc.yaml | 4 ++-
Documentation/writing-test-cases.rst | 14 ++++++++
src/ntfc/pytest/mypytest.py | 30 +++++++++++++---
tests/pytest/test_mypytest.py | 70 ++++++++++++++++++++++++++++++++++++
4 files changed, 112 insertions(+), 6 deletions(-)
diff --git a/Documentation/ntfc.yaml b/Documentation/ntfc.yaml
index 55558a1..a338833 100644
--- a/Documentation/ntfc.yaml
+++ b/Documentation/ntfc.yaml
@@ -5,4 +5,6 @@ dependencies: ["toml"] # python dependencies for test cases
module
requirements: # nuttx config requirements
- ["CONFIG_DEBUG_SYMBOLS", True]
- ["CONFIG_SYSTEM_NSH", True]
- - ["CONFIG_INIT_ENTRYPOINT", "nsh_main"]
+ # alternatives: any one entry satisfies the requirement;
+ # "*" accepts any set value
+ - [["CONFIG_INIT_ENTRYPOINT", "nsh_main"], ["CONFIG_INIT_FILEPATH", "*"]]
diff --git a/Documentation/writing-test-cases.rst
b/Documentation/writing-test-cases.rst
index b13a59b..7637292 100644
--- a/Documentation/writing-test-cases.rst
+++ b/Documentation/writing-test-cases.rst
@@ -70,6 +70,20 @@ String/Value Requirements:
- ["CONFIG_INIT_ENTRYPOINT", "nsh_main"] # CONFIG must equal value
- ["CONFIG_TASK_NAME_SIZE", "32"] # CONFIG must equal value
+Wildcard Requirements:
+
+.. code-block:: yaml
+
+ requirements:
+ - ["CONFIG_INIT_FILEPATH", "*"] # CONFIG must be set (any value)
+
+Alternative Requirements (any one entry satisfies the requirement):
+
+.. code-block:: yaml
+
+ requirements:
+ - [["CONFIG_INIT_ENTRYPOINT", "nsh_main"], ["CONFIG_INIT_FILEPATH", "*"]]
+
How Requirements Work:
1. NTFC reads NuttX ``.config`` file from configuration
diff --git a/src/ntfc/pytest/mypytest.py b/src/ntfc/pytest/mypytest.py
index b85f695..5a1c15d 100644
--- a/src/ntfc/pytest/mypytest.py
+++ b/src/ntfc/pytest/mypytest.py
@@ -124,14 +124,31 @@ class MyPytest:
json.dump(self._config.config, f, indent=2, sort_keys=True)
f.write("\n")
+ def _req_satisfied(self, product: Product, core: int, req: Any) -> bool:
+ """Check a single ntfc.yaml requirement entry.
+
+ Supported entry forms:
+
+ - ``[key, value]``: config value must equal ``value``
+ - ``[key, "*"]``: any truthy config value satisfies
+ - ``[[key, value], ...]``: alternatives, any one satisfies
+ """
+ if isinstance(req[0], list):
+ return any(self._req_satisfied(product, core, r) for r in req)
+
+ value = product.conf.kv_check(req[0], core)
+ if req[1] == "*":
+ return bool(value)
+ return bool(value == req[1])
+
def _kv_validate(
self, product: Product, core: int
- ) -> Tuple[bool, Optional[Any]]: # pragma: no cover
+ ) -> Tuple[bool, Optional[Any]]:
"""Check if configuration can be used with this tool."""
requirements = pytest.ntfcyaml.get("requirements", {})
for req in requirements:
- if product.conf.kv_check(req[0], core) != req[1]:
+ if not self._req_satisfied(product, core, req):
return False, req
return True, None
@@ -143,9 +160,12 @@ class MyPytest:
# check config requirements only on cores that participate in tests
for core in p.conf.active_core_indices:
- ret = self._kv_validate(p, core)
- if ret[0] is False: # pragma: no cover
- raise IOError(f"Missing kconfig dependency: {ret[1]}")
+ ok, req = self._kv_validate(p, core)
+ if not ok:
+ raise IOError(
+ f"product '{p.conf.name}' core {core}: "
+ f"missing kconfig requirement: {req}"
+ )
tmp.append(p)
diff --git a/tests/pytest/test_mypytest.py b/tests/pytest/test_mypytest.py
index c8affd5..6686b4f 100644
--- a/tests/pytest/test_mypytest.py
+++ b/tests/pytest/test_mypytest.py
@@ -212,6 +212,76 @@ def
test_create_products_skips_requirements_for_flash_only_core(
assert products[0].cores == ["cpuapp"]
+def test_create_products_requirement_forms(device_dummy, monkeypatch):
+ import pytest as pytest_module
+
+ config = {
+ "config": {},
+ "product": {
+ "name": "product",
+ "cores": {
+ "core0": {
+ "name": "cpuapp",
+ "device": "sim",
+ "conf_path": "./tests/resources/nuttx/sim/kv_config",
+ "elf_path": "./tests/resources/nuttx/sim/nuttx",
+ },
+ },
+ },
+ }
+
+ monkeypatch.setattr(
+ pytest_module,
+ "ntfcyaml",
+ {
+ "requirements": [
+ # plain form: exact match
+ ["CONFIG_HOST_LINUX", True],
+ # alternatives: first unset, second matches
+ [
+ ["CONFIG_INIT_FILEPATH", "*"],
+ ["CONFIG_INIT_ENTRYPOINT", "nsh_main"],
+ ],
+ # wildcard: any truthy value
+ ["CONFIG_NSH_PROMPT_STRING", "*"],
+ ]
+ },
+ raising=False,
+ )
+ with patch("ntfc.cores.get_device", return_value=device_dummy):
+ products = MyPytest(config)._create_products(EnvConfig(config))
+ assert len(products) == 1
+
+
+def test_create_products_requirement_unmet(device_dummy, monkeypatch):
+ import pytest as pytest_module
+
+ config = {
+ "config": {},
+ "product": {
+ "name": "product",
+ "cores": {
+ "core0": {
+ "name": "cpuapp",
+ "device": "sim",
+ "conf_path": "./tests/resources/nuttx/sim/kv_config",
+ "elf_path": "./tests/resources/nuttx/sim/nuttx",
+ },
+ },
+ },
+ }
+
+ monkeypatch.setattr(
+ pytest_module,
+ "ntfcyaml",
+ {"requirements": [["CONFIG_INIT_FILEPATH", "*"]]},
+ raising=False,
+ )
+ with patch("ntfc.cores.get_device", return_value=device_dummy):
+ with pytest.raises(IOError, match="product.*core 0.*INIT_FILEPATH"):
+ MyPytest(config)._create_products(EnvConfig(config))
+
+
def test_device_stop_calls_stop(config_dummy, device_dummy):
"""_device_stop calls device.stop() for each core."""
with patch("ntfc.cores.get_device", return_value=device_dummy):