[docs] 1# @Test suite for a src-trace file scope naming no file, TEST_MISSING_FILE_1, test, [IMPL_DISC_1]
 2"""A ``:file:`` scope naming no file warns at the directive, and the build goes on (#2069).
 3
 4Every case builds a copy of ``doc_test/need_id_refs`` (see ``test_need_id_refs``) whose
 5``index`` traces ``:file: traced.cpp`` only: a one-line need and a reference.
 6"""
 7
 8from pathlib import Path
 9
10import sphinx
11from sphinx.testing.util import SphinxTestApp
12from sphinx.util.console import strip_colors
13
14from sphinx_needs_testkit import build_warnings
15
16from .test_need_id_refs import FIXTURE, _build, _json, _MakeApp, _project, _refs
17
18#: Sphinx 8 renders a warning's ``[type.subtype]`` itself; 7.4 does not
19_SHOWS_WARNING_TYPES = sphinx.version_info >= (8,)
20
21INDEX = (FIXTURE / "docs" / "index.rst").read_text(encoding="utf-8")
22DIRECTIVE = ".. src-trace::\n   :project: src\n"
23assert DIRECTIVE in INDEX
24#: the line of ``index``'s directive, where the warning is located
25LINE = INDEX.splitlines().index(".. src-trace::") + 1
26
27FILE_SCOPE = {
28    "docs/index.rst": INDEX.replace(DIRECTIVE, f"{DIRECTIVE}   :file: traced.cpp\n")
29}
30SOURCE = "// @traced need, IMPL_TRACED\n// @need-ids: REQ_002\n"
31
32
33def _status(app: SphinxTestApp) -> str:
34    return strip_colors(app._status.getvalue())
35
36
37def _missing(root: Path) -> str:
38    suffix = " [codelinks.missing_file]" if _SHOWS_WARNING_TYPES else ""
39    src = (root / "src").resolve().as_posix()
40    return (
41        f"<srcdir>/index.rst:{LINE}: WARNING: src-trace: traced.cpp is not a file "
42        f"below {src}{suffix}"
43    )
44
45
46def _codelinks_warnings(app: SphinxTestApp) -> list[str]:
47    """The build's warnings but the unknown-id one every fixture build has, and the
48    node re-registration a second application in one test reports."""
49    return [
50        w
51        for w in build_warnings(app)
52        if "NOSUCH_ID" not in w and "already registered" not in w
53    ]
54
55
56def test_a_removed_file_warns_at_the_directive_and_the_build_goes_on(
57    tmp_path: Path, make_app: _MakeApp
58) -> None:
59    """The target removed between builds: the document is read again, warns once at
60    the directive, and its need and references are gone; re-created, the next build
61    reads the document again and they are back."""
62    _project(tmp_path, files={**FILE_SCOPE, "src/traced.cpp": SOURCE})
63    first = _build(tmp_path, make_app)
64    assert "IMPL_TRACED" in _json(first)["needs"]
65
66    (tmp_path / "src" / "traced.cpp").unlink()
67    removed = _build(tmp_path, make_app, freshenv=False)
68
69    assert "0 added, 1 changed, 0 removed" in _status(removed)
70    assert _codelinks_warnings(removed) == [_missing(tmp_path)]
71    assert "IMPL_TRACED" not in _json(removed)["needs"]
72    assert _refs(removed)["REQ_002"] is None
73
74    (tmp_path / "src" / "traced.cpp").write_text(SOURCE, encoding="utf-8")
75    back = _build(tmp_path, make_app, freshenv=False)
76
77    assert "0 added, 1 changed, 0 removed" in _status(back)
78    assert "IMPL_TRACED" in _json(back)["needs"]
79    assert _refs(back)["REQ_002"] is not None
80
81
82def test_a_file_scope_naming_no_file_warns_in_a_fresh_build(
83    tmp_path: Path, make_app: _MakeApp
84) -> None:
85    _project(tmp_path, files=FILE_SCOPE)
86    app = _build(tmp_path, make_app)
87
88    assert _codelinks_warnings(app) == [_missing(tmp_path)]
89    assert "IMPL_TRACED" not in _json(app)["needs"]