From f5da69dd0b054f017f70e356f3961e908074cf23 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 8 Feb 2024 18:16:35 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=F0=9F=91=8C=20Centralise=20nee?= =?UTF-8?q?d=20missing=20link=20reporting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, warnings for need outgoing links that reference a non-existent ID, are only generated "indirectly" in the render phase; where `NeedOutgoing` nodes are converted to nodes that are understood by sphinx builders (HTML, PDF, ...). Since this render phase is no longer needed/run for simply creating the `needs.json`, using the `needs` builder, these warnings are not generated. Additionally, any needs that are not explicitly rendered in the documentation, like externally imported needs, are also skipped. The reporting of unknown links is now moved to the `check_links` function, meaning it is now run for all needs and all builders. The warnings have also been given a subtype `link_outgoing` or `external_link_outgoing`, meaning they can be suppressed using the standard Sphinx config: `suppress_warnings = ["needs.link_outgoing", "needs.external_link_outgoing"]` This deprecates the need for the `needs_report_dead_links` configuration, which now emits a deprecation warning if set by the user. --- docs/conf.py | 2 +- docs/configuration.rst | 8 ++- sphinx_needs/config.py | 1 + sphinx_needs/directives/need.py | 29 ++++++-- sphinx_needs/needs.py | 44 ++++++------ sphinx_needs/roles/need_outgoing.py | 39 ++-------- sphinx_needs/roles/need_ref.py | 3 +- tests/conftest.py | 14 ++-- .../doc_report_dead_links_false/conf.py | 2 +- tests/test_basic_doc.py | 24 +++++-- tests/test_broken_links.py | 17 +++-- tests/test_github_issues.py | 14 ++-- tests/test_needs_external_needs_build.py | 5 +- tests/test_report_dead_links.py | 72 +++++++++---------- 14 files changed, 148 insertions(+), 126 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0c8d8188e..c9826cfd5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -90,7 +90,7 @@ # Absolute path to the needs_report_template_file based on the conf.py directory # needs_report_template = "/needs_templates/report_template.need" # Use custom report template -needs_report_dead_links = False +suppress_warnings = ["needs.link_outgoing"] needs_types = [ # Architecture types diff --git a/docs/configuration.rst b/docs/configuration.rst index 6d59ab252..627060ff9 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -294,7 +294,13 @@ In this cases, you can provide a list of tuples. needs_report_dead_links ~~~~~~~~~~~~~~~~~~~~~~~ -Deactivate/activate log messages of outgoing dead links. If set to ``False``, then deactivate. +.. deprecated:: 2.1.0 + + Instead add ``needs.link_outgoing`` to the `suppress_warnings `__ list:: + + suppress_warnings = ["needs.link_outgoing"] + +Deactivate/activate log messages of disallowed outgoing dead links. If set to ``False``, then deactivate. Default value is ``True``. diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py index 6b7daf4a3..a988074fe 100644 --- a/sphinx_needs/config.py +++ b/sphinx_needs/config.py @@ -191,6 +191,7 @@ def __setattr__(self, name: str, value: Any) -> None: Example: [{"name": "blocks, "incoming": "is blocked by", "copy_link": True, "color": "#ffcc00"}] """ report_dead_links: bool = field(default=True, metadata={"rebuild": "html", "types": (bool,)}) + """DEPRECATED: Use ``suppress_warnings = ["needs.link_outgoing"]`` instead.""" filter_data: dict[str, Any] = field(default_factory=dict, metadata={"rebuild": "html", "types": ()}) allow_unsafe_filters: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)}) flow_show_links: bool = field(default=False, metadata={"rebuild": "html", "types": (bool,)}) diff --git a/sphinx_needs/directives/need.py b/sphinx_needs/directives/need.py index 3e38947b5..b58df3890 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -29,7 +29,7 @@ from sphinx_needs.nodes import Need from sphinx_needs.utils import add_doc, profile, remove_node_from_tree, split_need_id -logger = get_logger(__name__) +LOGGER = get_logger(__name__) NON_BREAKING_SPACE = re.compile("\xa0+") @@ -156,7 +156,7 @@ def read_in_links(self, name: str) -> List[str]: if links_string: for link in re.split(r";|,", links_string): if link.isspace(): - logger.warning( + LOGGER.warning( f"Grubby link definition found in need '{self.trimmed_title}'. " "Defined link contains spaces only. [needs]", type="needs", @@ -436,10 +436,10 @@ def check_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig) -> N the ``has_forbidden_dead_links`` field is also added. """ extra_links = config.extra_links + report_dead_links = config.report_dead_links for need in needs.values(): for link_type in extra_links: - dead_links_allowed = link_type.get("allow_dead_links", False) - need_link_value: List[str] = ( + need_link_value = ( [need[link_type["option"]]] if isinstance(need[link_type["option"]], str) else need[link_type["option"]] # type: ignore ) for need_id_full in need_link_value: @@ -449,9 +449,26 @@ def check_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig) -> N need_id_main in needs and need_id_part and need_id_part not in needs[need_id_main]["parts"] ): need["has_dead_links"] = True - if not dead_links_allowed: + if not link_type.get("allow_dead_links", False): need["has_forbidden_dead_links"] = True - break # One found dead link is enough + if report_dead_links: + message = f"Need '{need['id']}' has unknown outgoing link '{link}' in field '{link_type['option']}'" + # if the need has been imported from an external URL, + # we want to provide that URL as the location of the warning, + # otherwise we use the location of the need in the source file + if need.get("is_external", False): + LOGGER.warning( + f"{need['external_url']}: {message} [needs.external_link_outgoing]", + type="needs", + subtype="external_link_outgoing", + ) + else: + LOGGER.warning( + f"{message} [needs.link_outgoing]", + location=(need["docname"], need["lineno"]), + type="needs", + subtype="link_outgoing", + ) def create_back_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig) -> None: diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index 077d5d65b..03bbce6c0 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -132,11 +132,12 @@ NeedFunc: process_need_func, } +LOGGER = get_logger(__name__) + def setup(app: Sphinx) -> Dict[str, Any]: - log = get_logger(__name__) - log.debug("Starting setup of Sphinx-Needs") - log.debug("Load Sphinx-Data-Viewer for Sphinx-Needs") + LOGGER.debug("Starting setup of Sphinx-Needs") + LOGGER.debug("Load Sphinx-Data-Viewer for Sphinx-Needs") app.setup_extension("sphinx_data_viewer") app.setup_extension("sphinxcontrib.jquery") @@ -304,12 +305,10 @@ def load_config(app: Sphinx, *_args: Any) -> None: """ Register extra options and directive based on config from conf.py """ - log = get_logger(__name__) - needs_config = NeedsSphinxConfig(app.config) if isinstance(needs_config.extra_options, dict): - log.info( + LOGGER.info( 'Config option "needs_extra_options" supports list and dict. However new default type since ' "Sphinx-Needs 0.7.2 is list. Please see docs for details." ) @@ -317,7 +316,9 @@ def load_config(app: Sphinx, *_args: Any) -> None: extra_options = NEEDS_CONFIG.extra_options for option in needs_config.extra_options: if option in extra_options: - log.warning(f'extra_option "{option}" already registered. [needs.config]', type="needs", subtype="config") + LOGGER.warning( + f'extra_option "{option}" already registered. [needs.config]', type="needs", subtype="config" + ) NEEDS_CONFIG.extra_options[option] = directives.unchanged # Get extra links and create a dictionary of needed options. @@ -393,17 +394,24 @@ def load_config(app: Sphinx, *_args: Any) -> None: if name not in NEEDS_CONFIG.warnings: NEEDS_CONFIG.warnings[name] = check else: - log.warning( + LOGGER.warning( f"{name!r} in 'needs_warnings' is already registered. [needs.config]", type="needs", subtype="config" ) if needs_config.constraints_failed_color: - log.warning( + LOGGER.warning( 'Config option "needs_constraints_failed_color" is deprecated. Please use "needs_constraint_failed_options" styles instead. [needs.config]', type="needs", subtype="config", ) + if needs_config.report_dead_links is not True: + LOGGER.warning( + 'Config option "needs_constraints_failed_color" is deprecated. Please use `suppress_warnings = ["needs.link_outgoing"]` instead. [needs.config]', + type="needs", + subtype="config", + ) + def visitor_dummy(*_args: Any, **_kwargs: Any) -> None: """ @@ -497,19 +505,15 @@ def prepare_env(app: Sphinx, env: BuildEnvironment, _docname: str) -> None: def check_configuration(_app: Sphinx, config: Config) -> None: - """ - Checks the configuration for invalid options. + """Checks the configuration for invalid options. E.g. defined need-option, which is already defined internally - - :param app: - :param config: - :return: """ - extra_options = config["needs_extra_options"] - link_types = [x["option"] for x in config["needs_extra_links"]] + needs_config = NeedsSphinxConfig(config) + extra_options = needs_config.extra_options + link_types = [x["option"] for x in needs_config.extra_links] - external_filter = getattr(config, "needs_filter_data", {}) + external_filter = needs_config.filter_data for extern_filter, value in external_filter.items(): # Check if external filter values is really a string if not isinstance(value, str): @@ -545,8 +549,8 @@ def check_configuration(_app: Sphinx, config: Config) -> None: " This is not allowed.".format(link + "_back") ) - external_variants = getattr(config, "needs_variants", {}) - external_variant_options = getattr(config, "needs_variant_options", []) + external_variants = needs_config.variants + external_variant_options = needs_config.variant_options for value in external_variants.values(): # Check if external filter values is really a string if not isinstance(value, str): diff --git a/sphinx_needs/roles/need_outgoing.py b/sphinx_needs/roles/need_outgoing.py index be119c735..8248f1ce4 100644 --- a/sphinx_needs/roles/need_outgoing.py +++ b/sphinx_needs/roles/need_outgoing.py @@ -23,7 +23,8 @@ def process_need_outgoing( builder = app.builder env = app.env needs_config = NeedsSphinxConfig(app.config) - report_dead_links = needs_config.report_dead_links + link_lookup = {link["option"]: link for link in needs_config.extra_links} + # for node_need_ref in doctree.findall(NeedOutgoing): for node_need_ref in found_nodes: node_link_container = nodes.inline() @@ -107,39 +108,11 @@ def process_need_outgoing( dead_link_para.append(dead_link_text) node_link_container += dead_link_para - extra_links = getattr(env.config, "needs_extra_links", []) - extra_links_dict = {x["option"]: x for x in extra_links} - - # Reduce log level to INFO, if dead links are allowed - if ( - "allow_dead_links" in extra_links_dict[link_type] - and extra_links_dict[link_type]["allow_dead_links"] - ): - log_level = "INFO" - kwargs = {} - else: - # Set an extra css class, if link type is not configured to allow dead links + # add a CSS class for disallowed unknown links + # note a warning is already emitted when validating the needs list + # so we don't need to do it here + if not link_lookup.get(link_type, {}).get("allow_dead_links", False): dead_link_para.attributes["classes"].append("forbidden") - log_level = "WARNING" - kwargs = {"type": "needs"} - - if report_dead_links: - if node_need_ref and node_need_ref.line: - log.log( - log_level, - f"linked need {need_id_main} not found " - f"(Line {node_need_ref.line} of file {node_need_ref.source}) [needs]", - **kwargs, - ) - else: - log.log( - log_level, - "outgoing linked need {} not found (document: {}, " - "source need {} on line {} ) [needs]".format( - need_id_main, ref_need["docname"], ref_need["id"], ref_need["lineno"] - ), - **kwargs, - ) # If we have several links, we add an empty text between them if (index + 1) < len(link_list): diff --git a/sphinx_needs/roles/need_ref.py b/sphinx_needs/roles/need_ref.py index 8795cde24..2f1526a13 100644 --- a/sphinx_needs/roles/need_ref.py +++ b/sphinx_needs/roles/need_ref.py @@ -141,8 +141,9 @@ def process_need_ref(app: Sphinx, doctree: nodes.document, fromdocname: str, fou else: log.warning( - f"linked need {node_need_ref['reftarget']} not found [needs]", + f"linked need {node_need_ref['reftarget']} not found [needs.link_ref]", type="needs", + subtype="link_ref", location=node_need_ref, ) diff --git a/tests/conftest.py b/tests/conftest.py index 2e679dc21..c631b27fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,7 @@ from sphinx import version_info from sphinx.application import Sphinx from sphinx.testing.path import path +from sphinx.testing.util import SphinxTestApp from syrupy.extensions.single_file import SingleFileSnapshotExtension, WriteMode from xprocess import ProcessStarter @@ -231,10 +232,11 @@ def test_app(make_app, sphinx_test_tempdir, request): builder_params = request.param sphinx_conf_overrides = builder_params.get("confoverrides", {}) - # Since we don't want copy the plantuml.jar file for each test function, - # we need to override the plantuml conf variable and set it to what we have already - plantuml = "java -Djava.awt.headless=true -jar %s" % os.path.join(sphinx_test_tempdir, "utils", "plantuml.jar") - sphinx_conf_overrides.update(plantuml=plantuml) + if not builder_params.get("no_plantuml", False): + # Since we don't want copy the plantuml.jar file for each test function, + # we need to override the plantuml conf variable and set it to what we have already + plantuml = "java -Djava.awt.headless=true -jar %s" % os.path.join(sphinx_test_tempdir, "utils", "plantuml.jar") + sphinx_conf_overrides.update(plantuml=plantuml) # copy test srcdir to test temporary directory sphinx_test_tempdir srcdir = builder_params.get("srcdir") @@ -245,7 +247,7 @@ def test_app(make_app, sphinx_test_tempdir, request): src_dir = Path(str(src_dir)) # return sphinx.testing fixture make_app and new srcdir which is in sphinx_test_tempdir - app: Sphinx = make_app( + app: SphinxTestApp = make_app( buildername=builder_params.get("buildername", "html"), srcdir=src_dir, freshenv=builder_params.get("freshenv"), @@ -268,6 +270,8 @@ def test_app(make_app, sphinx_test_tempdir, request): yield app + app.cleanup() + # Clean up the srcdir of each Sphinx app after the test function has executed if request.config.getoption("--sn-build-dir") is None: shutil.rmtree(parent_path, ignore_errors=True) diff --git a/tests/doc_test/doc_report_dead_links_false/conf.py b/tests/doc_test/doc_report_dead_links_false/conf.py index 3e75ba02f..813f445a4 100644 --- a/tests/doc_test/doc_report_dead_links_false/conf.py +++ b/tests/doc_test/doc_report_dead_links_false/conf.py @@ -10,7 +10,7 @@ {"directive": "test", "title": "Test Case", "prefix": "TC_", "color": "#DCB239", "style": "node"}, ] -needs_report_dead_links = False +suppress_warnings = ["needs.link_outgoing"] needs_extra_links = [ { diff --git a/tests/test_basic_doc.py b/tests/test_basic_doc.py index d47906aca..3c12b8ac7 100644 --- a/tests/test_basic_doc.py +++ b/tests/test_basic_doc.py @@ -9,7 +9,9 @@ import pytest import responses +from sphinx import version_info from sphinx.application import Sphinx +from sphinx.testing.util import SphinxTestApp from syrupy.filters import props from sphinx_needs.api.need import NeedsNoIdException @@ -234,14 +236,24 @@ def test_sphinx_api_build(): temp_dir = tempfile.mkdtemp() src_dir = os.path.join(os.path.dirname(__file__), "doc_test", "doc_basic") - sphinx_app = Sphinx( + if version_info >= (7, 2): + src_dir = Path(src_dir) + temp_dir = Path(temp_dir) + else: + from sphinx.testing.path import path + + src_dir = path(src_dir) + temp_dir = path(temp_dir) + + sphinx_app = SphinxTestApp( srcdir=src_dir, - confdir=src_dir, - outdir=temp_dir, - doctreedir=temp_dir, + builddir=temp_dir, buildername="html", parallel=4, freshenv=True, ) - sphinx_app.build() - assert sphinx_app.statuscode == 0 + try: + sphinx_app.build() + assert sphinx_app.statuscode == 0 + finally: + sphinx_app.cleanup() diff --git a/tests/test_broken_links.py b/tests/test_broken_links.py index 4b2710749..0bc1613b9 100644 --- a/tests/test_broken_links.py +++ b/tests/test_broken_links.py @@ -1,13 +1,18 @@ import pytest +from sphinx.util.console import strip_colors -@pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/broken_links"}], indirect=True) +@pytest.mark.parametrize( + "test_app", [{"buildername": "html", "srcdir": "doc_test/broken_links", "no_plantuml": True}], indirect=True +) def test_doc_build_html(test_app): app = test_app app.build() - warning = app._warning - # stdout warnings - warnings = warning.getvalue() - - assert "linked need BROKEN_LINK not found" in warnings + # check there are expected warnings + warnings = strip_colors(app._warning.getvalue().replace(str(app.srcdir), "srcdir")) + print(warnings.splitlines()) + assert warnings.splitlines() == [ + "srcdir/index.rst:12: WARNING: Need 'SP_TOO_002' has unknown outgoing link 'NOT_WORKING_LINK' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:21: WARNING: linked need BROKEN_LINK not found [needs.link_ref]", + ] diff --git a/tests/test_github_issues.py b/tests/test_github_issues.py index 2551af045..717f37e5f 100644 --- a/tests/test_github_issues.py +++ b/tests/test_github_issues.py @@ -1,6 +1,6 @@ import re +import subprocess from pathlib import Path -from subprocess import STDOUT, check_output import pytest @@ -16,9 +16,10 @@ def test_doc_github_44(test_app): # So we call the needed command directly, but still use the sphinx_testing app to create the outdir for us. app = test_app - output = str( - check_output(["sphinx-build", "-a", "-E", "-b", "html", app.srcdir, app.outdir], stderr=STDOUT, text=True) + output = subprocess.run( + ["sphinx-build", "-a", "-E", "-b", "html", app.srcdir, app.outdir], check=True, capture_output=True ) + # app.build() Uncomment, if build should stop on breakpoints html = Path(app.outdir, "index.html").read_text() assert "

Github Issue 44 test" in html @@ -26,8 +27,11 @@ def test_doc_github_44(test_app): assert "Test 2" in html assert "Test 3" in html - assert "linked need test_3 not found" not in output - assert "outgoing linked need test_123_broken not found" in output + stderr = output.stderr.decode("utf-8") + stderr = stderr.replace(str(app.srcdir), "srcdir") + assert stderr.splitlines() == [ + "srcdir/index.rst:11: WARNING: Need 'test_3' has unknown outgoing link 'test_123_broken' in field 'links' [needs.link_outgoing]" + ] @pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/doc_github_issue_61"}], indirect=True) diff --git a/tests/test_needs_external_needs_build.py b/tests/test_needs_external_needs_build.py index 0a3c8876b..a5a4ad0a8 100644 --- a/tests/test_needs_external_needs_build.py +++ b/tests/test_needs_external_needs_build.py @@ -22,7 +22,10 @@ def test_doc_build_html(test_app, sphinx_test_tempdir): output = subprocess.run( ["sphinx-build", "-b", "html", "-D", rf"plantuml={plantuml}", src_dir, out_dir], capture_output=True ) - assert not output.stderr, f"Build failed with stderr: {output.stderr}" + assert output.stderr.decode("utf-8").splitlines() == [ + "WARNING: http://my_company.com/docs/v1/index.html#TEST_01: Need 'EXT_TEST_01' has unknown outgoing link 'SPEC_1' in field 'links' [needs.external_link_outgoing]", + "WARNING: ../../_build/html/index.html#TEST_01: Need 'EXT_REL_PATH_TEST_01' has unknown outgoing link 'SPEC_1' in field 'links' [needs.external_link_outgoing]", + ] # run second time and check output_second = subprocess.run( diff --git a/tests/test_report_dead_links.py b/tests/test_report_dead_links.py index 24a8d9008..fd6f88aa0 100644 --- a/tests/test_report_dead_links.py +++ b/tests/test_report_dead_links.py @@ -1,3 +1,4 @@ +import subprocess from pathlib import Path import pytest @@ -6,61 +7,52 @@ @pytest.mark.parametrize( "test_app", [{"buildername": "html", "srcdir": "doc_test/doc_report_dead_links_true"}], indirect=True ) -def test_needs_report_dead_links_true(test_app): - import subprocess - +def test_needs_dead_links_warnings(test_app): app = test_app - # Check config value of needs_report_dead_links - assert app.config.needs_report_dead_links - src_dir = Path(app.srcdir) out_dir = Path(app.outdir) output = subprocess.run(["sphinx-build", "-M", "html", src_dir, out_dir], capture_output=True) - # Check log info msg of dead links - assert ( - "outgoing linked need DEAD_LINK_ALLOWED not found (document: index, source need REQ_001 on line 7 ) [needs]" - in output.stdout.decode("utf-8") - ) - # Check log warning msg of dead links - assert ( - "WARNING: outgoing linked need ANOTHER_DEAD_LINK not found (document: index, " - "source need REQ_004 on line 17 ) [needs]" in output.stderr.decode("utf-8") - ) - assert ( - "WARNING: outgoing linked need REQ_005 not found (document: index, source need TEST_004 on line 45 ) [needs]" - in output.stderr.decode("utf-8") - ) + # check there are expected warnings + stderr = output.stderr.decode("utf-8") + stderr = stderr.replace(str(src_dir), "srcdir") + assert stderr.splitlines() == [ + "srcdir/index.rst:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + ] @pytest.mark.parametrize( - "test_app", [{"buildername": "html", "srcdir": "doc_test/doc_report_dead_links_false"}], indirect=True + "test_app", [{"buildername": "needs", "srcdir": "doc_test/doc_report_dead_links_true"}], indirect=True ) -def test_needs_report_dead_links_false(test_app): - import subprocess - +def test_needs_dead_links_warnings_needs_builder(test_app): app = test_app - # Check config value of needs_report_dead_links - assert not app.config.needs_report_dead_links + src_dir = Path(app.srcdir) + out_dir = Path(app.outdir) + output = subprocess.run(["sphinx-build", "-M", "needs", src_dir, out_dir], capture_output=True) + + # check there are expected warnings + stderr = output.stderr.decode("utf-8") + stderr = stderr.replace(str(src_dir), "srcdir") + assert stderr.splitlines() == [ + "srcdir/index.rst:17: WARNING: Need 'REQ_004' has unknown outgoing link 'ANOTHER_DEAD_LINK' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'links' [needs.link_outgoing]", + "srcdir/index.rst:45: WARNING: Need 'TEST_004' has unknown outgoing link 'REQ_005.invalid' in field 'tests' [needs.link_outgoing]", + ] + + +@pytest.mark.parametrize( + "test_app", [{"buildername": "html", "srcdir": "doc_test/doc_report_dead_links_false"}], indirect=True +) +def test_needs_dead_links_suppress_warnings(test_app): + app = test_app src_dir = Path(app.srcdir) out_dir = Path(app.outdir) output = subprocess.run(["sphinx-build", "-M", "html", src_dir, out_dir], capture_output=True) - # Check log info msg of dead links deactivated - assert ( - "outgoing linked need DEAD_LINK_ALLOWED not found (document: index, source need REQ_001 on line 7 ) [needs]" - not in output.stdout.decode("utf-8") - ) - # Check log warning msg of dead links deactivated - assert ( - "WARNING: outgoing linked need ANOTHER_DEAD_LINK not found (document: index, " - "source need REQ_004 on line 17 ) [needs]" not in output.stderr.decode("utf-8") - ) - assert ( - "WARNING: outgoing linked need REQ_005 not found (document: index, source need TEST_004 on line 45 ) [needs]" - not in output.stderr.decode("utf-8") - ) + # check there are no warnings assert not output.stderr From ffc7be02bf6f2404d2b6856a775fb3523c77c924 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 15 Feb 2024 15:32:01 +0100 Subject: [PATCH 2/2] Update need.py --- sphinx_needs/directives/need.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx_needs/directives/need.py b/sphinx_needs/directives/need.py index b58df3890..5965e71a4 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -452,7 +452,7 @@ def check_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig) -> N if not link_type.get("allow_dead_links", False): need["has_forbidden_dead_links"] = True if report_dead_links: - message = f"Need '{need['id']}' has unknown outgoing link '{link}' in field '{link_type['option']}'" + message = f"Need '{need['id']}' has unknown outgoing link '{need_id_full}' in field '{link_type['option']}'" # if the need has been imported from an external URL, # we want to provide that URL as the location of the warning, # otherwise we use the location of the need in the source file