Skip to content

Commit

Permalink
Add unique name checks and tests (#11)
Browse files Browse the repository at this point in the history
Signed-off-by: romanodanilo <[email protected]>

Signed-off-by: romanodanilo <[email protected]>
  • Loading branch information
romanodanilo authored and hoangtungdinh committed Jul 3, 2024
1 parent 72ed71e commit 68e39a6
Show file tree
Hide file tree
Showing 14 changed files with 614 additions and 7 deletions.
4 changes: 4 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from qc_openscenario import constants
from qc_openscenario.checks.schema_checker import schema_checker
from qc_openscenario.checks.basic_checker import basic_checker
from qc_openscenario.checks.reference_checker import reference_checker

logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)

Expand Down Expand Up @@ -51,6 +52,9 @@ def main():
# 2. Run xml checks
schema_checker.run_checks(checker_data)

# 3. Run reference checks
reference_checker.run_checks(checker_data)

result.write_to_file(
config.get_checker_bundle_param(
checker_bundle_name=constants.BUNDLE_NAME, param_name="resultFile"
Expand Down
5 changes: 5 additions & 0 deletions qc_openscenario/checks/reference_checker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import reference_constants as reference_constants
from . import reference_checker as reference_checker
from . import (
uniquely_resolvable_entity_references as uniquely_resolvable_entity_references,
)
67 changes: 67 additions & 0 deletions qc_openscenario/checks/reference_checker/reference_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import logging

from lxml import etree

from qc_baselib import Configuration, Result, StatusType

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

from qc_openscenario.checks.reference_checker import (
reference_constants,
uniquely_resolvable_entity_references,
)


def run_checks(checker_data: models.CheckerData) -> None:
logging.info("Executing reference checks")

checker_data.result.register_checker(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
description="Check if xml properties of input file are properly set",
summary="",
)

# Skip if basic checks fail
if checker_data.input_file_xml_root is None:
logging.error(
f"Invalid xml input file. Checker {reference_constants.CHECKER_ID} skipped"
)
checker_data.result.set_checker_status(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
status=StatusType.SKIPPED,
)
return

# Skip if schema checks are skipped
if (
checker_data.result.get_checker_result("xoscBundle", "schema_xosc").status
is StatusType.SKIPPED
):
logging.error(
f"Schema checks have been skipped. Checker {reference_constants.CHECKER_ID} skipped"
)
checker_data.result.set_checker_status(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
status=StatusType.SKIPPED,
)
return

rule_list = [uniquely_resolvable_entity_references.check_rule]

for rule in rule_list:
rule(checker_data=checker_data)

logging.info(
f"Issues found - {checker_data.result.get_checker_issue_count(checker_bundle_name=constants.BUNDLE_NAME, checker_id=reference_constants.CHECKER_ID)}"
)

# TODO: Add logic to deal with error or to skip it
checker_data.result.set_checker_status(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
status=StatusType.COMPLETED,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CHECKER_ID = "reference_xosc"
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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"


def get_catalogs(root: etree._ElementTree) -> List[etree._ElementTree]:
catalogs = []

for catalog in root.iter("Catalog"):
catalogs.append(catalog)

return catalogs


def get_xpath(root: etree._ElementTree, element: etree._ElementTree) -> str:
return root.getpath(element)


def check_rule(checker_data: models.CheckerData) -> None:
"""
Implements a rule to check if referenced entities are unique
More info at
- https://github.com/asam-ev/qc-openscenarioxml/issues/8
"""
logging.info("Executing uniquely_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

rule_severity = IssueSeverity.WARNING
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.uniquely_resolvable_entity_references",
)

root = checker_data.input_file_xml_root

# List to store problematic nodes
errors = []

# Iterate over each 'Catalog' node
for catalog_node in root.findall(".//Catalog"):
# Dictionary to track child nodes by 'name' attribute
child_names = {}

# Iterate catalog children within current 'Catalog' node
# Currently the checks verifies that the same name is not used in the same
# catalog regardless the tag type
# E.g. Catalog children:
# # [Vehicle name="abc", Maneuver name="abc"]
# Will trigger the issue
for child_node in catalog_node.iterchildren():
name_attr = child_node.attrib.get("name")
if name_attr:
if name_attr in child_names:
errors.append(
{
"name": name_attr,
"tag": child_node.tag,
"first_xpath": get_xpath(root, child_names[name_attr]),
"duplicate_xpath": get_xpath(root, child_node),
}
)
else:
child_names[name_attr] = child_node

if len(errors) > 0:
issue_id = checker_data.result.register_issue(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
description="Issue flagging when referenced names are not unique",
level=rule_severity,
rule_uid=rule_uid,
)

for error in errors:
error_name = error["name"]
error_first_xpath = error["first_xpath"]
error_duplicate_xpath = error["duplicate_xpath"]
error_msg = f"Duplicate name {error_name}. First occurrence at {error_first_xpath} duplicate at {error_duplicate_xpath}"
logging.error(f"- Error: {error_msg}")
logging.error(f"- Duplicate xpath: {error_duplicate_xpath}")
checker_data.result.add_xml_location(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=reference_constants.CHECKER_ID,
issue_id=issue_id,
xpath=error_duplicate_xpath,
description=error_msg,
)
14 changes: 7 additions & 7 deletions qc_openscenario/checks/schema_checker/valid_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ def check_rule(checker_data: models.CheckerData) -> None:
)

if not schema_compliant:
issue_id = checker_data.result.register_issue(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=schema_constants.CHECKER_ID,
description="Issue flagging when input file does not follow its version schema",
level=IssueSeverity.ERROR,
rule_uid=rule_uid,
)

for error in errors:
issue_id = checker_data.result.register_issue(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=schema_constants.CHECKER_ID,
description="Issue flagging when input file does not follow its version schema",
level=IssueSeverity.ERROR,
rule_uid=rule_uid,
)
checker_data.result.add_file_location(
checker_bundle_name=constants.BUNDLE_NAME,
checker_id=schema_constants.CHECKER_ID,
Expand Down
32 changes: 32 additions & 0 deletions qc_openscenario/checks/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,35 @@ def get_standard_schema_version(root: etree._ElementTree) -> Union[str, None]:
header_attrib = header.attrib
version = f"{header_attrib['revMajor']}.{header_attrib['revMinor']}.0"
return version


def compare_versions(version1: str, version2: str) -> int:
"""Compare two version strings like "X.x.x"
This function is to avoid comparing version string basing on lexicographical order
that could cause problem. E.g.
1.10.0 > 1.2.0 but lexicographical comparison of string would return the opposite
Args:
version1 (str): First string to compare
version2 (str): Second string to compare
Returns:
int: 1 if version1 is bigger than version2. 0 if the version are the same. -1 otherwise
"""
v1_components = list(map(int, version1.split(".")))
v2_components = list(map(int, version2.split(".")))

# Compare each component until one is greater or they are equal
for v1, v2 in zip(v1_components, v2_components):
if v1 < v2:
return -1
elif v1 > v2:
return 1

# If all components are equal, compare based on length
if len(v1_components) < len(v2_components):
return -1
elif len(v1_components) > len(v2_components):
return 1
else:
return 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<OpenSCENARIO>
<FileHeader description="Vehicle Catalog" author="ASAM" revMajor="1" revMinor="2"
date="2023-11-15T11:00:00.000000" />
<Catalog name="VehicleCatalog_1">
<Vehicle name="car_white" vehicleCategory="car">
<BoundingBox>
<Center x="1.4" y="0.0" z="0.9" />
<Dimensions width="2.0" length="5.0" height="1.8" />
</BoundingBox>
<Performance maxSpeed="69" maxDeceleration="30" maxAcceleration="5" />
<Axles>
<FrontAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="2.98"
positionZ="0.4" />
<RearAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="0"
positionZ="0.4" />
</Axles>
<Properties>
<Property name="control" value="internal" />
<Property name="model_id" value="0" />
<File
filepath="../../../external/delivery/esmini-noosg/resources/osgb/car_white.osgb" />
</Properties>
</Vehicle>
<Vehicle name="car_red" />
<Controller name="abc" />
<Controller name="controller2" />
<Maneuver name="abc" />
<Maneuver name="maneuver2" />
<Route name="route1" />
<Route name="route2" />

</Catalog>
</OpenSCENARIO>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<OpenSCENARIO>
<FileHeader description="Vehicle Catalog" author="ASAM" revMajor="1" revMinor="2"
date="2023-11-15T11:00:00.000000" />
<Catalog name="VehicleCatalog_1">
<Vehicle name="car_white" vehicleCategory="car">
<BoundingBox>
<Center x="1.4" y="0.0" z="0.9" />
<Dimensions width="2.0" length="5.0" height="1.8" />
</BoundingBox>
<Performance maxSpeed="69" maxDeceleration="30" maxAcceleration="5" />
<Axles>
<FrontAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="2.98"
positionZ="0.4" />
<RearAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="0"
positionZ="0.4" />
</Axles>
<Properties>
<Property name="control" value="internal" />
<Property name="model_id" value="0" />
<File
filepath="../../../external/delivery/esmini-noosg/resources/osgb/car_white.osgb" />
</Properties>
</Vehicle>
<Vehicle name="car_red" />
<Controller name="controller1" />
<Controller name="controller2" />
<Maneuver name="maneuver1" />
<Maneuver name="maneuver2" />
<Route name="route1" />
<Route name="route2" />

</Catalog>
</OpenSCENARIO>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<OpenSCENARIO>
<FileHeader description="Vehicle Catalog" author="ASAM" revMajor="1" revMinor="2"
date="2023-11-15T11:00:00.000000" />
<Catalog name="VehicleCatalog">
<Vehicle name="car_white" vehicleCategory="car">
<BoundingBox>
<Center x="1.4" y="0.0" z="0.9" />
<Dimensions width="2.0" length="5.0" height="1.8" />
</BoundingBox>
<Performance maxSpeed="69" maxDeceleration="30" maxAcceleration="5" />
<Axles>
<FrontAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="2.98"
positionZ="0.4" />
<RearAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="0"
positionZ="0.4" />
</Axles>
<Properties>
<Property name="control" value="internal" />
<Property name="model_id" value="0" />
<File
filepath="../../../external/delivery/esmini-noosg/resources/osgb/car_white.osgb" />
</Properties>
</Vehicle>
<Vehicle name="car_white" vehicleCategory="car">
<BoundingBox>
<Center x="1.4" y="0.0" z="0.9" />
<Dimensions width="2.0" length="5.0" height="1.8" />
</BoundingBox>
<Performance maxSpeed="69" maxDeceleration="30" maxAcceleration="5" />
<Axles>
<FrontAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="2.98"
positionZ="0.4" />
<RearAxle maxSteering="30" wheelDiameter="0.8" trackWidth="1.68" positionX="0"
positionZ="0.4" />
</Axles>
<Properties>
<Property name="control" value="internal" />
<Property name="model_id" value="0" />
<File
filepath="../../../external/delivery/esmini-noosg/resources/osgb/car_white.osgb" />
</Properties>
</Vehicle>
</Catalog>
</OpenSCENARIO>
Loading

0 comments on commit 68e39a6

Please sign in to comment.