Skip to content

Commit

Permalink
Add no_use_of_undefined_import_prefixes check
Browse files Browse the repository at this point in the history
Signed-off-by: romanodanilo <[email protected]>
  • Loading branch information
romanodanilo committed Jul 19, 2024
1 parent 1102f21 commit cbbe8de
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 7 deletions.
1 change: 1 addition & 0 deletions qc_otx/checks/core_checker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from . import public_main_procedure as public_main_procedure
from . import mandatory_constant_initialization as mandatory_constant_initialization
from . import unique_node_names as unique_node_names
from . import no_use_of_undefined_import_prefixes as no_use_of_undefined_import_prefixes
2 changes: 2 additions & 0 deletions qc_otx/checks/core_checker/core_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public_main_procedure,
mandatory_constant_initialization,
unique_node_names,
no_use_of_undefined_import_prefixes,
)


Expand All @@ -35,6 +36,7 @@ def run_checks(checker_data: models.CheckerData) -> None:
document_name_package_uniqueness.check_rule, # Chk002
no_dead_import_links.check_rule, # Chk003
no_unused_imports.check_rule, # Chk004
no_use_of_undefined_import_prefixes.check_rule,
have_specification_if_no_realisation_exists.check_rule, # Chk007
public_main_procedure.check_rule, # Chk008
mandatory_constant_initialization.check_rule, # Chk009
Expand Down
14 changes: 8 additions & 6 deletions qc_otx/checks/core_checker/no_dead_import_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ def check_rule(checker_data: models.CheckerData) -> None:
import_prefix = import_node.get("prefix")
import_package = import_node.get("package")
import_document = import_node.get("document")
logging.debug(
f"import_prefix: {import_prefix} - import_package {import_package} - import_document {import_document}"
)
import_xpath = tree.getpath(import_node)
# Convert package name first.second.file.otx to filesystem path first/second/file.otx
import_package_splits = import_package.split(".")
import_path = ""
for import_package_element in import_package_splits:
import_path = os.path.join(import_path, import_package_element)
full_imported_path = os.path.join(import_path, import_document + ".otx")
# Import path checked in the same input file directory following
# Recommendation: Use only references inside the same package.
full_imported_path = os.path.join(import_document + ".otx")

logging.debug(f"cwd: {os.getcwd()}")
logging.debug(f"full_imported_path: {full_imported_path}")
# Check if file exists
import_file_exists = os.path.exists(full_imported_path)

Expand Down
88 changes: 88 additions & 0 deletions qc_otx/checks/core_checker/no_use_of_undefined_import_prefixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import logging, os

from typing import List

from lxml import etree

from qc_baselib import Result, IssueSeverity

from qc_otx import constants
from qc_otx.checks import models, utils

from qc_otx.checks.core_checker import core_constants

logging.basicConfig(level=logging.DEBUG)

RULE_SEVERITY = IssueSeverity.ERROR

OTX_LINK_ATTRIBUTES = set()
OTX_LINK_ATTRIBUTES.add("implements")
OTX_LINK_ATTRIBUTES.add("validFor")
OTX_LINK_ATTRIBUTES.add("procedure")
OTX_LINK_ATTRIBUTES.add("valueOf")
OTX_LINK_ATTRIBUTES.add("mutexLock")


def check_rule(checker_data: models.CheckerData) -> None:
"""
Implements core checker rule Core_Chk005
Criterion: If an imported name is accessed by prefix in an OtxLink type attribute,
the corresponding prefix definition shall exist in an <import> element.
Severity: Critical
"""
logging.info("Executing no_use_of_undefined_import_prefixes check")

rule_uid = checker_data.result.register_rule(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=core_constants.CHECKER_ID,
emanating_entity="asam.net",
standard="otx",
definition_setting="1.0.0",
rule_full_name="core.chk_005.no_use_of_undefined_import_prefixes",
)

tree = checker_data.input_file_xml_root
root = tree.getroot()

import_nodes = root.findall(".//import", namespaces=root.nsmap)

if import_nodes is None:
logging.error("No import nodes found. Skipping check..")
return

import_prefixes = [x.get("prefix") for x in import_nodes]

attributes = utils.get_all_attributes(tree, root)
otx_link_attributes = [x for x in attributes if x.name in OTX_LINK_ATTRIBUTES]

logging.debug(f"attributes: {attributes}")
logging.debug(f"import_prefixes: {import_prefixes}")
logging.debug(f"otx_link_attributes: {otx_link_attributes}")

for otx_link in otx_link_attributes:
if ":" not in otx_link.value:
continue
current_value_split = otx_link.value.split(":")
if len(current_value_split) == 0:
continue
current_prefix = current_value_split[0]

has_issue = current_prefix not in import_prefixes

if has_issue:
issue_id = checker_data.result.register_issue(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=core_constants.CHECKER_ID,
description="Issue flagging when prefix definition does not exists in an import element",
level=RULE_SEVERITY,
rule_uid=rule_uid,
)

checker_data.result.add_xml_location(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=core_constants.CHECKER_ID,
issue_id=issue_id,
xpath=otx_link.xpath,
description=f"Imported prefix {current_prefix} not found across import elements",
)
14 changes: 14 additions & 0 deletions qc_otx/checks/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
from dataclasses import dataclass
from lxml import etree
from typing import Union

from qc_baselib import Configuration, Result


@dataclass
class QueueNode:
element: etree._ElementTree
xpath: Union[str, None]


@dataclass
class AttributeInfo:
name: str
value: str
xpath: str


@dataclass
class CheckerData:
input_file_xml_root: etree._ElementTree
Expand Down
39 changes: 38 additions & 1 deletion qc_otx/checks/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
from lxml import etree
from typing import Union
from typing import Union, List
from qc_otx.checks.models import QueueNode, AttributeInfo


def get_all_attributes(
tree: etree._ElementTree, root: etree._Element
) -> List[AttributeInfo]:
"""Function to get all attributes in input xml document
Args:
tree (etree._ElementTree): the xml tree to analyse
root (etree._Element): the root node of the xml to analyse
Returns:
_type_: _description_
"""
attributes = []
stack = [
QueueNode(root, tree.getpath(root))
] # Initialize stack with the root element

while stack:
current_node = stack.pop()
current_element = current_node.element
current_xpath = current_node.xpath

# Process attributes of the current element
for attr, value in current_element.attrib.items():
attributes.append(AttributeInfo(attr, value, current_xpath))

# Push children to the stack for further processing
stack.extend(
reversed(
[QueueNode(x, tree.getpath(x)) for x in current_element.getchildren()]
)
)

return attributes


def get_standard_schema_version(root: etree._ElementTree) -> Union[str, None]:
Expand Down
File renamed without changes.
49 changes: 49 additions & 0 deletions tests/data/Core_Chk005/Core_Chk005_negative.otx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<otx id="2"
name="Core_Chk005_negative"
package="Core_Chk005"
version="1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
timestamp="2010-11-11T14:40:10">

<imports>
<import package="Core_Chk005" document="contexts" prefix="context" />
</imports>

<declarations>
<constant name="PI" visibility="PUBLIC" id="3-d1">
<specification>This defines global constant</specification>
<realisation>
<dataType xsi:type="Float">
<init value="3.14159265" />
</dataType>
</realisation>
</constant>
</declarations>

<validities>
<validity name="Workshop" id="4-v1">
<specification>Valid if executed in a workshop environment</specification>
<realisation xsi:type="IsEqual">
<!-- Inserted rule violation here - prefix not present among import nodes above-->
<term xsi:type="StringValue" valueOf="foo:LOCATION" />
<term xsi:type="StringLiteral" value="DealershopWorkshop" />
</realisation>
</validity>
<validity name="Assembly" id="4-v2">
<specification>Valid if executed at an assembly line</specification>
<realisation xsi:type="IsEqual">
<term xsi:type="StringValue" valueOf="context:LOCATION" />
<term xsi:type="StringLiteral" value="AssemblyLine" />
</realisation>
</validity>
</validities>

<action id="a1">
<realisation xsi:type="Assignment">
<result xsi:type="FloatVariable" name="foo" />
<term xsi:type="FloatValue" valueOf="PI" />
</realisation>
</action>

</otx>
48 changes: 48 additions & 0 deletions tests/data/Core_Chk005/Core_Chk005_positive.otx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<otx id="2"
name="Core_Chk005_positive"
package="Core_Chk005"
version="1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
timestamp="2010-11-11T14:40:10">

<imports>
<import package="Core_Chk005" document="contexts" prefix="context" />
</imports>

<declarations>
<constant name="PI" visibility="PUBLIC" id="3-d1">
<specification>This defines global constant</specification>
<realisation>
<dataType xsi:type="Float">
<init value="3.14159265" />
</dataType>
</realisation>
</constant>
</declarations>

<validities>
<validity name="Workshop" id="4-v1">
<specification>Valid if executed in a workshop environment</specification>
<realisation xsi:type="IsEqual">
<term xsi:type="StringValue" valueOf="context:LOCATION" />
<term xsi:type="StringLiteral" value="DealershopWorkshop" />
</realisation>
</validity>
<validity name="Assembly" id="4-v2">
<specification>Valid if executed at an assembly line</specification>
<realisation xsi:type="IsEqual">
<term xsi:type="StringValue" valueOf="context:LOCATION" />
<term xsi:type="StringLiteral" value="AssemblyLine" />
</realisation>
</validity>
</validities>

<action id="a1">
<realisation xsi:type="Assignment">
<result xsi:type="FloatVariable" name="foo" />
<term xsi:type="FloatValue" valueOf="PI" />
</realisation>
</action>

</otx>
9 changes: 9 additions & 0 deletions tests/data/Core_Chk005/contexts.otx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<otx xmlns="http://iso.org/OTX/1.0.0" id="1"
name="ImportExample"
package="Core_Chk005"
version="1.0"
timestamp="2010-03-18T14:40:10">

<specification> Example for showing the OTX document root structure </specification>
</otx>
45 changes: 45 additions & 0 deletions tests/test_core_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,51 @@ def test_chk004_negative(
test_utils.cleanup_files()


def test_chk005_positive(
monkeypatch,
) -> None:
base_path = "tests/data/Core_Chk005"
target_file_name = f"Core_Chk005_positive.otx"
target_file_path = os.path.join(base_path, target_file_name)

test_utils.create_test_config(target_file_path)

test_utils.launch_main(monkeypatch)

result = Result()
result.load_from_file(test_utils.REPORT_FILE_PATH)

core_issues = result.get_issues_by_rule_uid(
"asam.net:otx:1.0.0:core.chk_005.no_use_of_undefined_import_prefixes"
)
assert len(core_issues) == 0
test_utils.cleanup_files()


def test_chk005_negative(
monkeypatch,
) -> None:
base_path = "tests/data/Core_Chk005"
target_file_name = f"Core_Chk005_negative.otx"
target_file_path = os.path.join(base_path, target_file_name)

test_utils.create_test_config(target_file_path)

test_utils.launch_main(monkeypatch)

result = Result()
result.load_from_file(test_utils.REPORT_FILE_PATH)

core_issues = result.get_issues_by_rule_uid(
"asam.net:otx:1.0.0:core.chk_005.no_use_of_undefined_import_prefixes"
)
assert len(core_issues) == 1
assert core_issues[0].level == IssueSeverity.ERROR
assert "foo" in core_issues[0].locations[0].description

test_utils.cleanup_files()


def test_chk007_positive(
monkeypatch,
) -> None:
Expand Down

0 comments on commit cbbe8de

Please sign in to comment.