[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