[docs]  1# @Test suite for the one-line parser's warnings in a build, TEST_ONELINE_WARNINGS_1, test, [IMPL_OLP_1]
  2"""A malformed one-line marker is reported in the build, at its source line (#2076).
  3
  4Every case builds a copy of ``doc_test/need_id_refs`` (see ``test_need_id_refs``), whose
  5``index`` traces the whole of project ``src``, with ``src/refs.cpp`` reduced to one
  6reference that resolves, so that the only warnings a build can emit are the one-line
  7parser's.
  8"""
  9
 10import re
 11from pathlib import Path
 12
 13import pytest
 14from sphinx.util.console import strip_colors
 15from sphinx.util.parallel import parallel_available
 16
 17from sphinx_needs_testkit import assert_no_warnings, build_warnings
 18
 19from .test_config_only_refs import NO_DIRECTIVE
 20from .test_need_id_refs import _SHOWS_WARNING_TYPES, _build, _json, _MakeApp, _project
 21
 22#: ``refs.cpp`` with nothing to warn about
 23REFS = {"src/refs.cpp": "// @need-ids: REQ_001\nvoid implements_a() {}\n"}
 24
 25#: a ``[[ ... ]]`` style with two required fields, ``id`` and ``title``
 26BRACKETS = (
 27    "\n[codelinks.projects.src.analyse.oneline_comment_style]\n"
 28    'start_sequence = "[["\n'
 29    'end_sequence = "]]"\n'
 30    "needs_fields = [\n"
 31    '  { name = "id" },\n'
 32    '  { name = "title" },\n'
 33    '  { name = "type", default = "impl" },\n'
 34    '  { name = "links", type = "list[str]", default = [] },\n'
 35    "]\n"
 36)
 37#: line 3 is malformed: one field where two are required
 38BRACKETED = (
 39    "// [[ IMPL_1, first ]]\n"
 40    "void first() {}\n"
 41    "// [[ only-title ]]\n"
 42    "void second() {}\n"
 43    "// [[ IMPL_3, third ]]\n"
 44    "void third() {}\n"
 45)
 46#: the default ``@`` style on a Doxygen-documented file
 47DOXYGEN = (
 48    "/**\n"
 49    " * @brief Does a, IMPL_BRIEF\n"
 50    " * @param x the value\n"
 51    " * @return nothing\n"
 52    " * @see A, B, C, D, E\n"
 53    " */\n"
 54    "void documented(int x) {}\n"
 55)
 56
 57
 58def _oneline(location: str, message: str) -> str:
 59    suffix = " [codelinks.oneline]" if _SHOWS_WARNING_TYPES else ""
 60    return f"{location}: WARNING: {message}{suffix}"
 61
 62
 63TOO_FEW = _oneline("src/x.cpp:3", "too_few_fields: 1 given fields, minimum is 2")
 64
 65
 66def test_a_malformed_marker_warns_at_its_source_line(
 67    tmp_path: Path, make_app: _MakeApp
 68) -> None:
 69    """One warning, at the file and line, typed ``codelinks.oneline``; the build goes
 70    on and the valid markers' needs exist."""
 71    _project(tmp_path, toml_extra=BRACKETS, files={**REFS, "src/x.cpp": BRACKETED})
 72    app = _build(tmp_path, make_app)
 73
 74    assert app.statuscode == 0
 75    assert build_warnings(app) == [TOO_FEW]
 76    needs = _json(app)["needs"]
 77    assert {"IMPL_1", "IMPL_3"} <= set(needs)
 78
 79
 80def test_the_warning_is_suppressible(tmp_path: Path, make_app: _MakeApp) -> None:
 81    """One ``suppress_warnings`` entry silences every kind."""
 82    _project(tmp_path, toml_extra=BRACKETS, files={**REFS, "src/x.cpp": BRACKETED})
 83    app = _build(
 84        tmp_path,
 85        make_app,
 86        confoverrides={"suppress_warnings": ["codelinks.oneline"]},
 87    )
 88
 89    assert_no_warnings(app)
 90    assert {"IMPL_1", "IMPL_3"} <= set(_json(app)["needs"])
 91
 92
 93def test_documentation_tags_on_the_default_style_do_not_warn(
 94    tmp_path: Path, make_app: _MakeApp
 95) -> None:
 96    """On the default style, ``@param`` and ``@return`` lines are not markers, the
 97    ``brief`` line, which has the separator, is a need as it always was, and the
 98    ``see`` line, five fields where four are allowed, warns at its line."""
 99    _project(tmp_path, files={**REFS, "src/x.cpp": DOXYGEN})
100    app = _build(tmp_path, make_app)
101
102    assert build_warnings(app) == [
103        _oneline("src/x.cpp:5", "too_many_fields: 5 given fields, maximum is 4")
104    ]
105    assert _json(app)["needs"]["IMPL_BRIEF"]["title"] == "brief Does a"
106
107
108def test_a_config_only_project_reports_none(tmp_path: Path, make_app: _MakeApp) -> None:
109    """A project no directive traces creates no needs, so its malformed markers are
110    not reported; the summary line still counts its one-line needs."""
111    _project(
112        tmp_path,
113        toml_extra=BRACKETS,
114        files={**REFS, **NO_DIRECTIVE, "src/x.cpp": BRACKETED},
115    )
116    app = _build(tmp_path, make_app)
117
118    assert_no_warnings(app)
119    status = strip_colors(app._status.getvalue())
120    assert "2 one-line needs not created (no src-trace directive)" in status
121
122
123@pytest.mark.skipif(
124    not parallel_available, reason="Sphinx reads sources in parallel on POSIX only"
125)
126def test_a_worker_s_warning_reaches_the_log(tmp_path: Path, make_app: _MakeApp) -> None:
127    """``-j 2`` reads the directive in a worker; Sphinx forwards its log records."""
128    _project(tmp_path, toml_extra=BRACKETS, files={**REFS, "src/x.cpp": BRACKETED})
129    app = _build(tmp_path, make_app, parallel=2)
130
131    status = strip_colors(app._status.getvalue())
132    assert re.search(r"reading sources\.\.\. \[\s*\d+%\] \S+ \.\. \S+", status)
133    assert build_warnings(app) == [TOO_FEW]
134
135
136def test_a_need_sphinx_needs_refuses_warns_at_its_source_line(
137    tmp_path: Path, make_app: _MakeApp
138) -> None:
139    """A ``brief`` line that fits the default style but whose id, ``b``, fails
140    ``needs_id_regex``: one warning at its line, and the build goes on."""
141    _project(
142        tmp_path,
143        files={
144            **REFS,
145            "src/x.cpp": "// @brief Does a, b\nvoid f() {}\n// @Valid, IMPL_VALID\n",
146        },
147    )
148    app = _build(tmp_path, make_app)
149
150    assert app.statuscode == 0
151    assert build_warnings(app) == [
152        _oneline(
153            "src/x.cpp:1",
154            "invalid_id: one-line need could not be created: Given ID 'b' does not "
155            "match configured regex '^[A-Z0-9_]{5,}'",
156        )
157    ]
158    needs = _json(app)["needs"]
159    assert "b" not in needs
160    assert "IMPL_VALID" in needs