diff --git a/qc_openscenario/checks/reference_checker/__init__.py b/qc_openscenario/checks/reference_checker/__init__.py
index 52dc939..bb05131 100644
--- a/qc_openscenario/checks/reference_checker/__init__.py
+++ b/qc_openscenario/checks/reference_checker/__init__.py
@@ -12,3 +12,5 @@
from . import (
valid_actor_reference_in_private_actions as valid_actor_reference_in_private_actions,
)
+from . import resolvable_entity_references as resolvable_entity_references
+from . import resolvable_variable_reference as resolvable_variable_reference
diff --git a/qc_openscenario/checks/reference_checker/reference_checker.py b/qc_openscenario/checks/reference_checker/reference_checker.py
index 22d8b23..ecbd12b 100644
--- a/qc_openscenario/checks/reference_checker/reference_checker.py
+++ b/qc_openscenario/checks/reference_checker/reference_checker.py
@@ -13,6 +13,8 @@
resolvable_signal_id_in_traffic_signal_state_action,
resolvable_traffic_signal_controller_by_traffic_signal_controller_ref,
valid_actor_reference_in_private_actions,
+ resolvable_entity_references,
+ resolvable_variable_reference,
)
@@ -58,6 +60,8 @@ def run_checks(checker_data: models.CheckerData) -> None:
resolvable_signal_id_in_traffic_signal_state_action.check_rule,
resolvable_traffic_signal_controller_by_traffic_signal_controller_ref.check_rule,
valid_actor_reference_in_private_actions.check_rule,
+ resolvable_entity_references.check_rule,
+ resolvable_variable_reference.check_rule,
]
for rule in rule_list:
diff --git a/qc_openscenario/checks/reference_checker/resolvable_entity_references.py b/qc_openscenario/checks/reference_checker/resolvable_entity_references.py
new file mode 100644
index 0000000..7cb68c6
--- /dev/null
+++ b/qc_openscenario/checks/reference_checker/resolvable_entity_references.py
@@ -0,0 +1,98 @@
+import os, logging
+
+from dataclasses import dataclass
+from typing import List
+
+from lxml import etree
+
+from qc_baselib import Configuration, Result, IssueSeverity
+
+from qc_openscenario import constants
+from qc_openscenario.schema import schema_files
+from qc_openscenario.checks import utils, models
+
+from qc_openscenario.checks.reference_checker import reference_constants
+from collections import deque, defaultdict
+
+MIN_RULE_VERSION = "1.2.0"
+RULE_SEVERITY = IssueSeverity.ERROR
+
+
+def check_rule(checker_data: models.CheckerData) -> None:
+ """
+ Rule ID: asam.net:xosc:1.2.0:reference_control.resolvable_entity_references
+
+ Description: A named reference in the EntityRef must be resolvable. Checking all EntityRef's in the document.
+ Severity: ERROR
+
+ Version range: [1.2.0, )
+
+ Remark:
+ None
+
+ More info at
+ - https://github.com/asam-ev/qc-openscenarioxml/issues/15
+ """
+ logging.info("Executing resolvable_entity_references check")
+
+ schema_version = checker_data.schema_version
+ if schema_version is None:
+ logging.info(f"- Version not found in the file. Skipping check")
+ return
+
+ if utils.compare_versions(schema_version, MIN_RULE_VERSION) < 0:
+ logging.info(
+ f"- Version {schema_version} is less than minimum required version {MIN_RULE_VERSION}. Skipping check"
+ )
+ return
+
+ rule_uid = checker_data.result.register_rule(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ emanating_entity="asam.net",
+ standard="xosc",
+ definition_setting=MIN_RULE_VERSION,
+ rule_full_name="reference_control.resolvable_entity_references",
+ )
+
+ root = checker_data.input_file_xml_root
+
+ entities_node = root.find("Entities")
+ if entities_node is None:
+ logging.error("Cannot find Entities node in provided XOSC file. Skipping check")
+ return
+
+ defined_entities = set()
+ for entity_node in list(entities_node):
+ current_name = entity_node.get("name")
+ if current_name is not None:
+ defined_entities.add(current_name)
+
+ storyboard_node = root.find("Storyboard")
+ if storyboard_node is None:
+ logging.error(
+ "Cannot find Storyboard node in provided XOSC file. Skipping check"
+ )
+ return
+
+ nodes_with_entity_ref = storyboard_node.xpath(".//*[@entityRef]")
+
+ for node_with_entity_ref in nodes_with_entity_ref:
+ current_name = node_with_entity_ref.get("entityRef")
+ if current_name is not None and current_name not in defined_entities:
+ xpath = root.getpath(node_with_entity_ref)
+
+ issue_id = checker_data.result.register_issue(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ description="Issue flagging when an entity is referred in a entityRef attribute but it is not declared among Entities",
+ level=RULE_SEVERITY,
+ rule_uid=rule_uid,
+ )
+ checker_data.result.add_xml_location(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ issue_id=issue_id,
+ xpath=xpath,
+ description=f"Entity at {xpath} with id {current_name} not found among defined Entities ",
+ )
diff --git a/qc_openscenario/checks/reference_checker/resolvable_variable_reference.py b/qc_openscenario/checks/reference_checker/resolvable_variable_reference.py
new file mode 100644
index 0000000..102ced4
--- /dev/null
+++ b/qc_openscenario/checks/reference_checker/resolvable_variable_reference.py
@@ -0,0 +1,104 @@
+import os, logging
+
+from dataclasses import dataclass
+from typing import List
+
+from lxml import etree
+
+from qc_baselib import Configuration, Result, IssueSeverity
+
+from qc_openscenario import constants
+from qc_openscenario.schema import schema_files
+from qc_openscenario.checks import utils, models
+
+from qc_openscenario.checks.reference_checker import reference_constants
+from collections import deque, defaultdict
+
+MIN_RULE_VERSION = "1.2.0"
+RULE_SEVERITY = IssueSeverity.ERROR
+
+
+def check_rule(checker_data: models.CheckerData) -> None:
+ """
+ Rule ID: asam.net:xosc:1.2.0:reference_control.resolvable_variable_reference
+
+ Description: The VariableDeclaration according to the "variableRef" property must exist within the ScenarioDefinition.
+ Severity: ERROR
+
+ Version range: [1.2.0, )
+
+ Remark:
+ None
+
+ More info at
+ - https://github.com/asam-ev/qc-openscenarioxml/issues/18
+ """
+ logging.info("Executing resolvable_variable_reference check")
+
+ schema_version = checker_data.schema_version
+ if schema_version is None:
+ logging.info(f"- Version not found in the file. Skipping check")
+ return
+
+ if utils.compare_versions(schema_version, MIN_RULE_VERSION) < 0:
+ logging.info(
+ f"- Version {schema_version} is less than minimum required version {MIN_RULE_VERSION}. Skipping check"
+ )
+ return
+
+ rule_uid = checker_data.result.register_rule(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ emanating_entity="asam.net",
+ standard="xosc",
+ definition_setting=MIN_RULE_VERSION,
+ rule_full_name="reference_control.resolvable_variable_reference",
+ )
+
+ root = checker_data.input_file_xml_root
+
+ parameter_declaration_nodes = root.find("ParameterDeclarations")
+ variable_declaration_nodes = root.find("VariableDeclarations")
+
+ # Get parameters and variables declarations
+ defined_param_variables = set()
+ if parameter_declaration_nodes is not None:
+ for declaration_node in list(parameter_declaration_nodes):
+ current_name = declaration_node.get("name")
+ if current_name is not None:
+ defined_param_variables.add(current_name)
+
+ if variable_declaration_nodes is not None:
+ for declaration_node in list(variable_declaration_nodes):
+ current_name = declaration_node.get("name")
+ if current_name is not None:
+ defined_param_variables.add(current_name)
+
+ storyboard_node = root.find("Storyboard")
+ if storyboard_node is None:
+ logging.error(
+ "Cannot find Storyboard node in provided XOSC file. Skipping check"
+ )
+ return
+
+ nodes_with_variable_ref = storyboard_node.xpath(".//*[@variableRef]")
+
+ for node_with_variable_ref in nodes_with_variable_ref:
+ current_name = node_with_variable_ref.get("variableRef")
+ if current_name is not None and current_name not in defined_param_variables:
+ xpath = root.getpath(node_with_variable_ref)
+
+ issue_id = checker_data.result.register_issue(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ description="Issue flagging when a variable is referred in a variableRef attribute but it is not found within ScenarioDefinition",
+ level=RULE_SEVERITY,
+ rule_uid=rule_uid,
+ )
+ checker_data.result.add_xml_location(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ issue_id=issue_id,
+ xpath=xpath,
+ description=f"Variable with id {current_name} not found within ScenarioDefinition",
+ )
diff --git a/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.multiple.xosc b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.multiple.xosc
new file mode 100644
index 0000000..69ebfe6
--- /dev/null
+++ b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.multiple.xosc
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.xosc b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.xosc
new file mode 100644
index 0000000..ae1d99b
--- /dev/null
+++ b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.negative.xosc
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.positive.xosc b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.positive.xosc
new file mode 100644
index 0000000..dafd821
--- /dev/null
+++ b/tests/data/resolvable_entity_references/reference_control.resolvable_entity_references.positive.xosc
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.multiple.xosc b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.multiple.xosc
new file mode 100644
index 0000000..13a1ff5
--- /dev/null
+++ b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.multiple.xosc
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.xosc b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.xosc
new file mode 100644
index 0000000..3605f4f
--- /dev/null
+++ b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.negative.xosc
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.positive.xosc b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.positive.xosc
new file mode 100644
index 0000000..bc434cf
--- /dev/null
+++ b/tests/data/resolvable_variable_reference/reference_control.resolvable_variable_reference.positive.xosc
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_reference_checks.py b/tests/test_reference_checks.py
index b44121e..ea07003 100644
--- a/tests/test_reference_checks.py
+++ b/tests/test_reference_checks.py
@@ -397,3 +397,179 @@ def test_valid_actor_reference_in_private_actions_negative(
assert len(reference_issues) == 1
assert reference_issues[0].level == IssueSeverity.ERROR
test_utils.cleanup_files()
+
+
+def test_resolvable_entity_reference_positive(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_entity_references/"
+ target_file_name = f"reference_control.resolvable_entity_references.positive.xosc"
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+ assert (
+ len(
+ result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_entity_references"
+ )
+ )
+ == 0
+ )
+
+
+def test_resolvable_entity_reference_negative(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_entity_references/"
+ target_file_name = f"reference_control.resolvable_entity_references.negative.xosc"
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+
+ reference_issues = result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_entity_references"
+ )
+ assert len(reference_issues) == 1
+ assert reference_issues[0].level == IssueSeverity.ERROR
+ test_utils.cleanup_files()
+
+
+def test_resolvable_entity_reference_negative_multiple(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_entity_references/"
+ target_file_name = (
+ f"reference_control.resolvable_entity_references.negative.multiple.xosc"
+ )
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+
+ reference_issues = result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_entity_references"
+ )
+ assert len(reference_issues) == 2
+ assert reference_issues[0].level == IssueSeverity.ERROR
+ assert reference_issues[1].level == IssueSeverity.ERROR
+ assert "Vehicle 3" in reference_issues[0].locations[0].description
+ assert "Vehicle 4" in reference_issues[1].locations[0].description
+ test_utils.cleanup_files()
+
+
+def test_resolvable_variable_reference_positive(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_variable_reference/"
+ target_file_name = f"reference_control.resolvable_variable_reference.positive.xosc"
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+ assert (
+ len(
+ result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_variable_reference"
+ )
+ )
+ == 0
+ )
+
+
+def test_resolvable_variable_reference_negative(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_variable_reference/"
+ target_file_name = f"reference_control.resolvable_variable_reference.negative.xosc"
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+
+ reference_issues = result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_variable_reference"
+ )
+ assert len(reference_issues) == 1
+ assert reference_issues[0].level == IssueSeverity.ERROR
+ test_utils.cleanup_files()
+
+
+def test_resolvable_variable_reference_multiple(
+ monkeypatch,
+) -> None:
+ base_path = "tests/data/resolvable_variable_reference/"
+ target_file_name = (
+ f"reference_control.resolvable_variable_reference.negative.multiple.xosc"
+ )
+ 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)
+
+ _ = result.get_checker_result(
+ checker_bundle_name=constants.BUNDLE_NAME,
+ checker_id=reference_constants.CHECKER_ID,
+ )
+
+ reference_issues = result.get_issues_by_rule_uid(
+ "asam.net:xosc:1.2.0:reference_control.resolvable_variable_reference"
+ )
+ assert len(reference_issues) == 2
+ assert reference_issues[0].level == IssueSeverity.ERROR
+ assert reference_issues[1].level == IssueSeverity.ERROR
+ assert (
+ "unknownReferenceForYPosition" in reference_issues[0].locations[0].description
+ )
+ assert "foo" in reference_issues[1].locations[0].description
+ test_utils.cleanup_files()