Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderLanin committed Jan 27, 2025
1 parent 6dcbaf4 commit 8a1430d
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 376 deletions.
33 changes: 8 additions & 25 deletions docs/_tooling/extensions/score_metamodel/checks/traceability.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
# *******************************************************************************
from sphinx_needs.data import NeedsInfoType

from docs._tooling.extensions.score_metamodel import graph_check, CheckLogger, NeedsInfoType
from docs._tooling.extensions.score_metamodel import (
CheckLogger,
NeedsInfoType,
graph_check,
)
from docs._tooling.extensions.score_metamodel.checks.util import check_option


Expand All @@ -24,7 +28,7 @@ def check_linkage_parent(needs: dict[str, NeedsInfoType], log: CheckLogger) -> b
"""
parents_not_correct = []

for need in needs:
for need in needs.values():
for satisfie_need in need["satisfies"]:
if needs.get(satisfie_need, {}).get("status") != "valid":
parents_not_correct.append(satisfie_need)
Expand All @@ -41,7 +45,7 @@ def check_linkage_safety(needs: dict[str, NeedsInfoType], log: CheckLogger) -> b
"""
Checking if for feature, component and tool requirements it shall be checked if at least one parent requirement contains the same or lower ASIL compared to the ASIL of the current requirement then it will return False.
"""
for need in needs:
for need in needs.values():
if need["id"].startswith("TOOL_REQ") or need["id"].startswith(
"GD"
): ##TO REMOVE. when safety is defined for TOOL_REQ requirements
Expand Down Expand Up @@ -76,30 +80,9 @@ def check_linkage_status(needs: dict[str, NeedsInfoType], log: CheckLogger) -> b
"""
Checking if for valid feature, component and tool requirements it shall be checked if the status of the parent requirement is also valid then it will retun False.
"""
for need in needs:
for need in needs.values():
if need["status"] == "valid":
for satisfie_need in need["satisfies"]:
if needs.get(satisfie_need, {}).get("status") != "valid":
msg = f"Need: `{need['id']}` have a valid status but one of it's parents: `{satisfie_need}` have an invalid status. \n"
log.warning_for_option(need, "satisfies", msg)


def check_g_reqid_traceability(need: NeedsInfoType, log: CheckLogger) -> bool:
"""
Checking if all described and wanted attributes and restrictions in 'G_Req_Traceability' are followed and adhered to.
Returns 'True' if guidance has not been followed.
"""

## Actual checking logic.
if need["type"] == "stkh_req" and need["satisfies"] != []:
msg = "option 'satisfies' not allowed in 'stkh_req' directives."
log.warning_for_option(need, "satisfies", msg)
if need["type"] in ["feat_req", "comp_req", "tool_req"]:
msg = f"option 'satisfies' missing in {need['id']}. satisfies is requiered in `feat_req, comp_req, tool_req` directives."
check_option(
need=need,
option="satisfies",
log=log,
msg=msg,
allow_empty=False,
)
35 changes: 34 additions & 1 deletion docs/_tooling/extensions/score_metamodel/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from sphinx.util.logging import SphinxLoggerAdapter

from docs._tooling.extensions.score_metamodel import CheckLogger
from docs._tooling.extensions.score_metamodel import CheckLogger, NeedsInfoType


def fake_check_logger():
Expand All @@ -23,3 +23,36 @@ def fake_check_logger():
LOGGER = MagicMock(spec=SphinxLoggerAdapter)
LOGGER.warning = MagicMock()
return CheckLogger(LOGGER)

def need(**kwargs):
"""Convinience function to create a NeedsInfoType object with some defaults."""

kwargs.setdefault("id", "test_need")
kwargs.setdefault("docname", "docname")
kwargs.setdefault("doctype", "rst")
kwargs.setdefault("lineno", "42")

return NeedsInfoType(**kwargs)


def verify_log_string(logger: CheckLogger, expected_substring, expect_location=True):
"""
Assert that the logger was called exactly once with a message containing a specific substring.
This also verifies that the defaults from need() are used correctly.
So you must use need() to create the need object that is passed to the checks.
"""
logger._log.warning.assert_called_once()

# Retrieve the call arguments
args, kwargs = logger._log.warning.call_args
log_message = args[0]

assert expected_substring in log_message, \
f"Expected substring '{
expected_substring}' not found in log message: '{log_message}'"

assert kwargs["type"] == "score_metamodel"

if expect_location:
assert kwargs["location"] == "docname.rst:42"
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,7 @@

from docs._tooling.extensions.score_metamodel.checks.check_options import check_options
from docs._tooling.extensions.score_metamodel.log import CheckLogger
from docs._tooling.extensions.score_metamodel.tests import fake_check_logger


def need(type, **kwargs):
# TODO: allow kwargs to override the defaults
return NeedsInfoType(id="test_need",
type=type,
docname="docname",
doctype="rst",
lineno="42",
**kwargs)


def assert_logger_called_once(logger: CheckLogger, expected_substring, expect_location=True):
"""
Assert that the logger was called exactly once with a message containing a specific substring
and the expected keyword arguments.
"""
# Assert the logger was called exactly once
assert logger.has_warnings
assert isinstance(logger._log, MagicMock)
assert isinstance(logger._log.warning, MagicMock)
assert logger._log.call_count == 1
logger._log.assert_called_once()

# Retrieve the call arguments
args, kwargs = logger._log.call_args

# Check the message contains the expected substring
log_message = args[0]
assert expected_substring in log_message, \
f"Expected substring '{
expected_substring}' not found in log message: '{log_message}'"

assert kwargs["type"] == "score_metamodel"

if expect_location:
assert kwargs["location"] == "docname.rst:42"

from docs._tooling.extensions.score_metamodel.tests import fake_check_logger, need

NEED_TYPE_INFO = [
{
Expand All @@ -73,6 +35,8 @@ def assert_logger_called_once(logger: CheckLogger, expected_substring, expect_lo
# TestType="Requirements-based test",
# DerivationTechnique="Analysis of requirements",
# )


@pytest.mark.parametrize(
"need, expected_warning",
[
Expand Down Expand Up @@ -110,13 +74,6 @@ def test_check_options(need: NeedsInfoType, expected_warning: str | None):
# Then expect that the check prints the expected warning
if expected_warning:
assert logger.has_warnings
assert_logger_called_once(
logger, expected_warning, expect_location=True)
verify_log_string(logger, expected_warning)
else:
assert not logger.has_warnings



# TODO: do we want something like this?
# if __name__ == "__main__":
# sys.exit(pytest.main(args=["-vv", __file__]))
Loading

0 comments on commit 8a1430d

Please sign in to comment.