diff --git a/doc/manual/howto/render-test-specifications.rst b/doc/manual/howto/render-test-specifications.rst index d926fa7..3bff7d5 100644 --- a/doc/manual/howto/render-test-specifications.rst +++ b/doc/manual/howto/render-test-specifications.rst @@ -84,8 +84,9 @@ vocabulary rather than ``test_case``/``verifies``/etc., is a matching pair of :module: checks/widget/probe The argument is the Doxygen **module** group's name, never a suite; ``:module:`` -is a project-relative path used only to find that module's ``testcase.yaml`` -for the scenario table. +is a project-relative path used only to find that module's scenario file +for the scenario table. That file is the first of ``testcase.yaml``, +``tests.yaml`` and ``sample.yaml`` that exists, in twister's order. 6. Add the report half (optional) --------------------------------------- diff --git a/doc/manual/reference/directives-and-roles.rst b/doc/manual/reference/directives-and-roles.rst index 0c3666b..9336518 100644 --- a/doc/manual/reference/directives-and-roles.rst +++ b/doc/manual/reference/directives-and-roles.rst @@ -100,7 +100,8 @@ The chain from annotated ztest C source to a rendered, traceable test report ``testmodule``'s argument is a Doxygen ``@defgroup`` name (the *module* group, never a suite or a path); ``:module:`` is a project-relative path used only to -locate that module's ``testcase.yaml`` for the rendered scenario table. Every +locate that module's scenario file for the rendered scenario table: the first +of ``testcase.yaml``, ``tests.yaml`` and ``sample.yaml`` that exists. 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. diff --git a/sphinx/_extensions/_tests/test_rst_builders.py b/sphinx/_extensions/_tests/test_rst_builders.py index b6a23bb..f112253 100644 --- a/sphinx/_extensions/_tests/test_rst_builders.py +++ b/sphinx/_extensions/_tests/test_rst_builders.py @@ -196,6 +196,33 @@ def test_build_scenario_table_missing_yaml(): assert lines == [] +def test_find_scenario_yaml_takes_tests_yaml(tmp_path): + # Newer Zephyr trees name the file tests.yaml. + (tmp_path / "tests.yaml").write_text("tests: {}\n") + assert rb.find_scenario_yaml(tmp_path) == tmp_path / "tests.yaml" + + +def test_find_scenario_yaml_takes_sample_yaml(tmp_path): + (tmp_path / "sample.yaml").write_text("tests: {}\n") + assert rb.find_scenario_yaml(tmp_path) == tmp_path / "sample.yaml" + + +def test_find_scenario_yaml_follows_twister_order(tmp_path): + # Twister reads testcase.yaml first, so it wins over tests.yaml. + for name in ("testcase.yaml", "tests.yaml", "sample.yaml"): + (tmp_path / name).write_text("tests: {}\n") + assert rb.find_scenario_yaml(tmp_path) == tmp_path / "testcase.yaml" + + +def test_find_scenario_yaml_warns_with_the_names_it_tried(tmp_path, caplog): + with caplog.at_level("WARNING", logger=rb.logger.name): + assert rb.find_scenario_yaml(tmp_path) is None + message = caplog.text + assert str(tmp_path) in message + for name in ("testcase.yaml", "tests.yaml", "sample.yaml"): + assert name in message + + def test_build_procedure_need_rst_links_prose_but_keeps_title_plain(): # A need's title is not parsed as RST, so link markup there would render # verbatim; the details are parsed and link like the see-also line does. diff --git a/sphinx/_extensions/_tests/test_test_module.py b/sphinx/_extensions/_tests/test_test_module.py index 0dfba95..be2b532 100644 --- a/sphinx/_extensions/_tests/test_test_module.py +++ b/sphinx/_extensions/_tests/test_test_module.py @@ -406,7 +406,7 @@ def test_testmodule_directive_suite_heading(app): @pytest.mark.sphinx("html", srcdir=str(_ROOTS / "test-testmodule")) -def test_testmodule_directive_notes_its_xml_and_testcase_yaml_as_inputs(app): +def test_testmodule_directive_notes_its_xml_and_scenario_files_as_inputs(app): # Without these, an incremental build after the test sources change (a # retagged status, a renamed test) keeps the old test cases. app.build() @@ -414,7 +414,8 @@ def test_testmodule_directive_notes_its_xml_and_testcase_yaml_as_inputs(app): xml_dir = Path(app.config.testmodule_xml_dir) assert str(xml_dir / "index.xml") in inputs assert any(p.endswith(".xml") and "group__" in p for p in inputs) - assert any(p.endswith("testcase.yaml") for p in inputs) + for name in ("testcase.yaml", "tests.yaml", "sample.yaml"): + assert any(p.endswith(name) for p in inputs) @pytest.mark.sphinx("html", srcdir=str(_ROOTS / "test-testmodule")) diff --git a/sphinx/_extensions/rst_builders.py b/sphinx/_extensions/rst_builders.py index 581e15a..44319ff 100644 --- a/sphinx/_extensions/rst_builders.py +++ b/sphinx/_extensions/rst_builders.py @@ -23,6 +23,8 @@ "build_result_rst", "build_symbol_need_rst", "build_scenario_table", + "SCENARIO_YAML_NAMES", + "find_scenario_yaml", ] logger = logging.getLogger(__name__) @@ -392,8 +394,32 @@ def build_symbol_need_rst(info, need_names=None, depends_field=False): return "\n".join(lines) +# The names that twister reads for the scenarios of a test directory, in +# twister's order (scripts/pylib/twister/twisterlib/testplan.py). Newer Zephyr +# trees name the file tests.yaml, older ones testcase.yaml. +SCENARIO_YAML_NAMES = ("testcase.yaml", "tests.yaml", "sample.yaml") + + +def find_scenario_yaml(module_dir): + """Return the first scenario file in ``module_dir`` that exists, or None. + + The names come from ``SCENARIO_YAML_NAMES``. When no file exists, log a + warning that names each file it tried. + """ + module_dir = Path(module_dir) + for name in SCENARIO_YAML_NAMES: + path = module_dir / name + if path.is_file(): + return path + logger.warning( + f"testmodule: no scenario file in {module_dir} " + f"(tried {', '.join(SCENARIO_YAML_NAMES)})" + ) + return None + + def build_scenario_table(testcase_yaml_path): - """Return RST lines for a list-table of scenarios from testcase.yaml.""" + """Return RST lines for a list-table of scenarios from a scenario file.""" try: with open(testcase_yaml_path) as f: data = yaml.safe_load(f) diff --git a/sphinx/_extensions/test_module.py b/sphinx/_extensions/test_module.py index 06b8b66..06e18ee 100644 --- a/sphinx/_extensions/test_module.py +++ b/sphinx/_extensions/test_module.py @@ -20,11 +20,13 @@ from needs_fields import depends_field, field_type from rst_builders import ( RESULT_FIELD_ROLES, + SCENARIO_YAML_NAMES, _need_name, build_need_rst, build_procedure_need_rst, build_result_rst, build_scenario_table, + find_scenario_yaml, ) from sphinx.util import logging from twister_reader import ( @@ -629,9 +631,13 @@ def run(self): suite_refids, proc_refids = _classify_inner_groups(module_cdef, xml_dir) need_names = _need_names_from_config(app) - testcase_yaml = Path(module_root) / module_path / "testcase.yaml" - _note_input(env, testcase_yaml) - scenario_lines = build_scenario_table(testcase_yaml) + # Each name twister accepts is an input, so a scenario file that is + # added or renamed later also re-reads the document. + module_dir = Path(module_root) / module_path + for name in SCENARIO_YAML_NAMES: + _note_input(env, module_dir / name) + scenario_yaml = find_scenario_yaml(module_dir) + scenario_lines = build_scenario_table(scenario_yaml) if scenario_yaml else [] all_rst = list(scenario_lines) for suite_refid in suite_refids: all_rst += _build_suite_rst(