diff --git a/docs/vspec2id.md b/docs/vspec2id.md index 0de0592d..ec0705f6 100644 --- a/docs/vspec2id.md +++ b/docs/vspec2id.md @@ -1,6 +1,6 @@ # vspec2id - vspec static UID generator and validator -The vspecID.py script is used to generate and validate static UIDs for all nodes in the tree. +The vspec2id.py script is used to generate and validate static UIDs for all nodes in the tree. They will be used as unique identifiers to transmit data between nodes. The static UIDs are implemented to replace long strings like `Vehicle.Body.Lights.DirectionIndicator.Right.IsSignaling` with a 4-byte identifier. @@ -9,8 +9,8 @@ with a 4-byte identifier. ```bash usage: vspec2id.py [-h] [-I dir] [-e EXTENDED_ATTRIBUTES] [-s] [--abort-on-unknown-attribute] [--abort-on-name-style] [--format format] [--uuid] [--no-expand] [-o overlays] [-u unit_file] - [-vt vspec_types_file] [-ot ] [--json-all-extended-attributes] [--json-pretty] [--yaml-all-extended-attributes] [-v version] [--all-idl-features] - [--gqlfield GQLFIELD GQLFIELD] [--validate-static-uid VALIDATE_STATIC_UID] [--only-validate-no-export] + [-q quantity_file] [-vt vspec_types_file] [-ot ] [--yaml-all-extended-attributes] [-v version] [--all-idl-features] + [--validate-static-uid VALIDATE_STATIC_UID] [--only-validate-no-export] [--strict-mode] Convert vspec to other formats. @@ -27,6 +27,7 @@ IDGEN arguments: Path to validation file. --only-validate-no-export For pytests and pipelines you can skip the export of the + --strict-mode Strict mode means that the generation of static UIDs is case-sensitive. ``` ## Example @@ -43,6 +44,9 @@ cd path/to/your/vss-tools Great, you generated your first overlay that will also be used as your validation file as soon as you update your vehicle signal specification file. +If needed you can make the static UID generation case-sensitive using the command line argument `--strict-mode`. It +will default to false. + ### Generate e.g. yaml file with static UIDs Now if you just want to generate a new e.g. yaml file including your static UIDs, please use the overlay function of @@ -78,11 +82,28 @@ A.B.NewName: type: actuator allowed: ["YES", "NO"] description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] +``` +or +``` +A.B.NewName: + datatype: string + type: actuator + allowed: ["YES", "NO"] + description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. + fka: A.B.OlderName ``` -As stated if you want to rename the node `A.B.NewName` to `A.NewName` you can also write the `fka` attribute -stating its legacy path. +In order to add fka attribute, one can add fka directly into the vspec file or use overlay feature of vss-tools. + +Example mycustom-overlay-fka.vspec +``` +A.B.NewName: + datatype: string + type: actuator + fka: A.B.OlderName +``` +As stated if you want to rename the node `A.B.NewName` to `A.NewName` you can also write the Formerly Known As `fka` attribute stating its legacy path. For hashing function in previous case `A.B.OlderName` will be used. To summarize these are the `BREAKING CHANGES` that affect the hash and `NON-BREAKING CHANGES` that throw warnings only: @@ -93,14 +114,14 @@ warnings only: | Data type | | Deprecation | | Type (i.e. node type) | | Deleted Attribute | | Unit | | Change description | -| Enum values (allowed) | | | +| Enum values (allowed) | | Qualified name (fka) | | Minimum | | | | Maximum | | | Now you should know about all possible changes. To run the validation step, please do: ```bash -./vspecID.py ../vehicle_signal_specification/spec/VehicleSignalSpecification.vspec ../output_id_v2.vspec --validate-static-uid ../output_id_v1.vspec +./vspec2id.py ../vehicle_signal_specification/spec/VehicleSignalSpecification.vspec ../output_id_v2.vspec --validate-static-uid ../output_id_v1.vspec ``` Depending on what you changed in the vehicle signal specification the corresponding errors will be triggered. diff --git a/tests/vspec/test_static_uids/test_static_uids.py b/tests/vspec/test_static_uids/test_static_uids.py index 2aa8a6c5..43269326 100644 --- a/tests/vspec/test_static_uids/test_static_uids.py +++ b/tests/vspec/test_static_uids/test_static_uids.py @@ -11,19 +11,19 @@ # import os -import pytest import shlex -import vspec -import vspec.vssexporters.vss2id as vss2id -import vspec2x +from typing import Dict + +import pytest import yaml -from typing import Dict -from vspec.model.constants import VSSTreeType, VSSDataType, VSSUnit +import vspec +import vspec2x +import vspec.vssexporters.vss2id as vss2id +from vspec.model.constants import VSSDataType, VSSTreeType, VSSUnit from vspec.model.vsstree import VSSNode from vspec.utils.idgen_utils import get_all_keys_values - # HELPERS @@ -100,11 +100,51 @@ def test_generate_id( result_static_uid: str, ): node = get_test_node(node_name, unit, datatype, allowed, minimum, maximum) - result, _ = vss2id.generate_split_id(node, id_counter=0) + result, _ = vss2id.generate_split_id(node, id_counter=0, strict_mode=False) assert result == result_static_uid +@pytest.mark.parametrize( + "node_name_case_sensitive, node_name_case_insensitive, strict_mode", + [ + ("TestNode", "testnode", False), + ("TestNode", "testnode", True), + ], +) +def test_strict_mode( + node_name_case_sensitive: str, node_name_case_insensitive: str, strict_mode: bool +): + node_case_sensitive = get_test_node( + node_name=node_name_case_sensitive, + unit="m", + datatype=VSSDataType.FLOAT, + allowed="", + minimum="", + maximum="", + ) + result_case_sensitive, _ = vss2id.generate_split_id( + node_case_sensitive, id_counter=0, strict_mode=strict_mode + ) + + node_case_insensitive = get_test_node( + node_name=node_name_case_insensitive, + unit="m", + datatype=VSSDataType.FLOAT, + allowed="", + minimum="", + maximum="", + ) + result_case_insensitive, _ = vss2id.generate_split_id( + node_case_insensitive, id_counter=0, strict_mode=strict_mode + ) + + if strict_mode: + assert result_case_sensitive != result_case_insensitive + else: + assert result_case_sensitive == result_case_insensitive + + @pytest.mark.parametrize( "test_file, validation_file", [("test_vspecs/test.vspec", "./validation.yaml")], @@ -122,10 +162,10 @@ def test_export_node( vspec_file, include_paths=["."], tree_type=VSSTreeType.SIGNAL_TREE ) yaml_dict: Dict[str, str] = {} - vss2id.export_node(yaml_dict, tree, id_counter=0) + vss2id.export_node(yaml_dict, tree, id_counter=0, strict_mode=False) result_dict: Dict[str, str] - with open(os.path.join(dir_path, validation_file), "r") as f: + with open(os.path.join(dir_path, validation_file)) as f: result_dict = yaml.load(f, Loader=yaml.FullLoader) assert result_dict.keys() == yaml_dict.keys() @@ -149,11 +189,7 @@ def test_duplicate_hash(caplog: pytest.LogCaptureFixture, children_names: list): if children_names[0] == children_names[1]: # assert system exit and log with pytest.raises(SystemExit) as pytest_wrapped_e: - vss2id.export_node( - yaml_dict, - tree, - id_counter=0, - ) + vss2id.export_node(yaml_dict, tree, id_counter=0, strict_mode=False) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == -1 assert len(caplog.records) == 1 and all( @@ -161,11 +197,7 @@ def test_duplicate_hash(caplog: pytest.LogCaptureFixture, children_names: list): ) else: # assert all IDs different - vss2id.export_node( - yaml_dict, - tree, - id_counter=0, - ) + vss2id.export_node(yaml_dict, tree, id_counter=0, strict_mode=False) assigned_ids: list = [] for key, value in get_all_keys_values(yaml_dict): if not isinstance(value, dict) and key == "staticUID": @@ -186,8 +218,14 @@ def test_full_script(caplog: pytest.LogCaptureFixture): @pytest.mark.usefixtures("change_test_dir") -def test_semantic(caplog: pytest.LogCaptureFixture): - validation_file: str = "./validation_vspecs/validation_semantic_change.vspec" +@pytest.mark.parametrize( + "validation_file", + [ + "./validation_vspecs/validation_semantic_change_1.vspec", + "./validation_vspecs/validation_semantic_change_2.vspec", + ], +) +def test_semantic(caplog: pytest.LogCaptureFixture, validation_file: str): clas = shlex.split(get_cla_validation(validation_file)) vspec2x.main(["--format", "idgen"] + clas[1:]) @@ -203,7 +241,6 @@ def test_vss_path(caplog: pytest.LogCaptureFixture): test_file: str = "./test_vspecs/test_vss_path.vspec" clas = shlex.split(get_cla_test(test_file)) vspec2x.main(["--format", "idgen"] + clas[1:]) - assert len(caplog.records) == 1 and all( log.levelname == "WARNING" for log in caplog.records ) diff --git a/tests/vspec/test_static_uids/test_vspecs/test.vspec b/tests/vspec/test_static_uids/test_vspecs/test.vspec index bb5ec60d..58769626 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_added_attribute.vspec b/tests/vspec/test_static_uids/test_vspecs/test_added_attribute.vspec index 8c5d4e4c..ca93b2c6 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_added_attribute.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_added_attribute.vspec @@ -40,7 +40,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_datatype.vspec b/tests/vspec/test_static_uids/test_vspecs/test_datatype.vspec index 34ee2983..ccb89c88 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_datatype.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_datatype.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_deleted_attribute.vspec b/tests/vspec/test_static_uids/test_vspecs/test_deleted_attribute.vspec index a28b973a..bf2df77e 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_deleted_attribute.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_deleted_attribute.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_deprecation.vspec b/tests/vspec/test_static_uids/test_vspecs/test_deprecation.vspec index 2c1bc8fd..03e441c7 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_deprecation.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_deprecation.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_description.vspec b/tests/vspec/test_static_uids/test_vspecs/test_description.vspec index 336ca49a..99452558 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_description.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_description.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_name_datatype.vspec b/tests/vspec/test_static_uids/test_vspecs/test_name_datatype.vspec index 8df7e62e..897182ea 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_name_datatype.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_name_datatype.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_unit.vspec b/tests/vspec/test_static_uids/test_vspecs/test_unit.vspec index 6ec0a3a2..e70db936 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_unit.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_unit.vspec @@ -41,7 +41,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/test_vspecs/test_vss_path.vspec b/tests/vspec/test_static_uids/test_vspecs/test_vss_path.vspec index ca11d073..4f3d5556 100644 --- a/tests/vspec/test_static_uids/test_vspecs/test_vss_path.vspec +++ b/tests/vspec/test_static_uids/test_vspecs/test_vss_path.vspec @@ -42,7 +42,7 @@ A.B.NewName: type: sensor unit: mm description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. - fka: ['A.B.OldName', 'A.B.OlderName'] + fka: ['A.B.OlderName', 'A.B.OldName'] A.B.IsLeaf: datatype: string type: actuator diff --git a/tests/vspec/test_static_uids/validation.yaml b/tests/vspec/test_static_uids/validation.yaml index ee6c6b7e..f7fe3e78 100644 --- a/tests/vspec/test_static_uids/validation.yaml +++ b/tests/vspec/test_static_uids/validation.yaml @@ -38,9 +38,9 @@ A.B.NewName: datatype: uint32 description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. fka: - - A.B.OldName - A.B.OlderName - staticUID: '0x126503B6' + - A.B.OldName + staticUID: '0x3FB9A236' type: sensor unit: mm A.Float: diff --git a/tests/vspec/test_static_uids/validation_vspecs/validation.vspec b/tests/vspec/test_static_uids/validation_vspecs/validation.vspec index 065f3e76..8587c1a4 100644 --- a/tests/vspec/test_static_uids/validation_vspecs/validation.vspec +++ b/tests/vspec/test_static_uids/validation_vspecs/validation.vspec @@ -46,9 +46,9 @@ A.B.NewName: datatype: uint32 description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. fka: - - A.B.OldName - A.B.OlderName - staticUID: '0x126503B6' + - A.B.OldName + staticUID: '0x3FB9A236' type: sensor unit: mm A.Float: diff --git a/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change.vspec b/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_1.vspec similarity index 98% rename from tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change.vspec rename to tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_1.vspec index 2c7b8007..f4541431 100644 --- a/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change.vspec +++ b/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_1.vspec @@ -47,7 +47,7 @@ A.B.OldName: description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. fka: - A.B.OlderName - staticUID: '0x8B7DE17D' + staticUID: '0x3FB9A236' type: sensor unit: mm A.Float: diff --git a/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_2.vspec b/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_2.vspec new file mode 100644 index 00000000..21fd81e7 --- /dev/null +++ b/tests/vspec/test_static_uids/validation_vspecs/validation_semantic_change_2.vspec @@ -0,0 +1,74 @@ +# Copyright (c) 2023 Contributors to COVESA +# +# This program and the accompanying materials are made available under the +# terms of the Mozilla Public License 2.0 which is available at +# https://www.mozilla.org/en-US/MPL/2.0/ +# +# SPDX-License-Identifier: MPL-2.0 + +A: + description: A is a test node + staticUID: '0x5B38F4AC' + type: branch +A.B: + description: B is a branch of A + staticUID: '0x4687862C' + type: branch +A.B.Int32: + datatype: int32 + description: A.B.Int32 is a leaf of A.B of datatype int32 + staticUID: '0xF59053DF' + type: sensor + unit: rpm +A.B.IsLeaf: + allowed: + - 'YES' + - 'NO' + datatype: string + description: This node is a leaf of the tree and it has allowed values (aka an enum). + staticUID: '0x41EF3218' + type: actuator +A.B.Max: + datatype: uint8 + description: A leaf that uses a maximum value. + max: 100 + staticUID: '0xCF708DCC' + type: sensor + unit: percent +A.B.Min: + datatype: uint8 + description: A leaf that uses a minimum value. + min: 10 + staticUID: '0x2FC6BA2E' + type: sensor + unit: percent +A.B.OlderName: + datatype: uint32 + description: A.B.NewName's old name is 'OldName'. And its even older name is 'OlderName'. + staticUID: '0x3FB9A236' + type: sensor + unit: mm +A.Float: + datatype: float + description: A.Float is a leaf of A of datatype float + staticUID: '0xA493DFBF' + type: actuator + unit: mm +A.Int16: + datatype: int16 + description: A.Int16 is a leaf of A of datatype int16 + staticUID: '0x3D9D89A3' + type: sensor + unit: rpm +A.String: + datatype: string + deprecation: This is test deprecation, let's say it used to be called Str instead + String. + description: A.String is a leaf of A of datatype string + staticUID: '0x49A622FC' + type: sensor +A.StringArray: + datatype: string[] + description: A.StringArray is a leaf of A of datatype string array + staticUID: '0xE8A4A62B' + type: sensor diff --git a/vspec/utils/idgen_utils.py b/vspec/utils/idgen_utils.py index cd9fd85e..39b409d7 100644 --- a/vspec/utils/idgen_utils.py +++ b/vspec/utils/idgen_utils.py @@ -6,6 +6,7 @@ # # SPDX-License-Identifier: MPL-2.0 + def get_node_identifier_bytes( qualified_name: str, data_type: str, @@ -14,6 +15,7 @@ def get_node_identifier_bytes( allowed: str, minimum: str, maximum: str, + strict_mode: bool, ) -> bytes: """Get a node identifier as bytes. Used as an input for hashing @@ -24,10 +26,11 @@ def get_node_identifier_bytes( @param allowed: the enum for allowed values @param minimum: min value for the data if exists @param maximum: max value for the data if exists + @param strict_mode: strict mode means case sensitivity of node qualified names @return: a bytes representation of the node """ - return ( + node_identifier: bytes = ( f"{qualified_name}: " f"unit: {unit}, " f"datatype: {data_type}, " @@ -35,7 +38,12 @@ def get_node_identifier_bytes( f"allowed: {allowed}" f"min: {minimum}" f"max: {maximum}" - ).encode("utf-8").lower() + ).encode() + + if strict_mode: + return node_identifier + else: + return node_identifier.lower() def fnv1_32_hash(identifier: bytes) -> int: @@ -52,25 +60,32 @@ def fnv1_32_hash(identifier: bytes) -> int: return id_hash -def fnv1_32_wrapper(name: str, source: dict): +def fnv1_32_wrapper(name: str, source: dict, strict_mode: bool): """A wrapper for the 32-bit hashing function if the input node is represented as dict instead of VSSNode @param name: full name aka qualified name @param source: + @param strict_mode: strict mode means case sensitivity of node qualified names @return: """ - allowed: str = source["allowed"] if "allowed" in source.keys() else "" - minimum: str = source["min"] if "min" in source.keys() else "" - maximum: str = source["max"] if "max" in source.keys() else "" + # Verify and assign values from source dictionary using source.get + allowed: str = source.get("allowed", "") + minimum: str = source.get("min", "") + maximum: str = source.get("max", "") + datatype: str = source.get("datatype", "") + vsstype: str = source.get("type", "") + unit: str = source.get("unit", "") + identifier = get_node_identifier_bytes( name, - source["datatype"], - source["type"], - source["unit"], + datatype, + vsstype, + unit, allowed, minimum, maximum, + strict_mode, ) return format(fnv1_32_hash(identifier), "08X") diff --git a/vspec/utils/vss2id_val.py b/vspec/utils/vss2id_val.py index c5a6bb2f..c5be37cb 100644 --- a/vspec/utils/vss2id_val.py +++ b/vspec/utils/vss2id_val.py @@ -6,11 +6,13 @@ # # SPDX-License-Identifier: MPL-2.0 -from anytree import PreOrderIter # type: ignore import argparse import logging import sys from typing import Optional + +from anytree import PreOrderIter # type: ignore + from vspec.model.vsstree import VSSNode from vspec.utils.idgen_utils import fnv1_32_wrapper @@ -40,19 +42,20 @@ def check_description(k: str, v: dict, match_tuple: tuple): f"vspec: '{v['description']}'" ) - def check_semantics(k: str, v: dict) -> Optional[int]: + def check_semantics(k: str, v: dict, strict_mode: bool) -> Optional[int]: """Checks if the change was a semantic or path change. This can be triggered by manually adding a fka (formerly known as) attribute to the vspec. The result is that the old hash can be matched such that a node keeps the same UID. @param k: the current key @param v: the current value (dict) + @param strict_mode: strict mode means case sensitivity for static UID generation @return: boolean if it was a semantic or path change """ if "fka" in v.keys(): semantic_match: Optional[int] = None for fka_val in v["fka"]: - old_static_uid = "0x" + fnv1_32_wrapper(fka_val, v) + old_static_uid = "0x" + fnv1_32_wrapper(fka_val, v, strict_mode) for i, validation_node in enumerate(validation_tree_nodes): if ( old_static_uid @@ -98,15 +101,15 @@ def hashed_pipeline(): nonlocal validation_tree_nodes for key, value in signals_dict.items(): - matched_uids = [ - (key, id_validation_tree) - for id_validation_tree, other_node in enumerate(validation_tree_nodes) - if value["staticUID"] == other_node.extended_attributes["staticUID"] - ] - + matched_uids = [] + for id_validation_tree, other_node in enumerate(validation_tree_nodes): + if value["staticUID"] == other_node.extended_attributes["staticUID"]: + if key != other_node.qualified_name(): + _ = check_semantics(key, value, config.strict_mode) + matched_uids.append((key, id_validation_tree)) # if not matched via UID check semantics or path change if len(matched_uids) == 0: - semantic_match = check_semantics(key, value) + semantic_match = check_semantics(key, value, config.strict_mode) if semantic_match is None: key_found: bool = False for i, node in enumerate(validation_tree_nodes): diff --git a/vspec/vssexporters/vss2id.py b/vspec/vssexporters/vss2id.py index 93f87d57..3a38c0a2 100644 --- a/vspec/vssexporters/vss2id.py +++ b/vspec/vssexporters/vss2id.py @@ -15,17 +15,16 @@ import os import sys from typing import Dict, Tuple + +import yaml + from vspec import load_tree -from vspec.model.constants import VSSTreeType from vspec.loggingconfig import initLogging +from vspec.model.constants import VSSTreeType from vspec.model.vsstree import VSSNode from vspec.utils import vss2id_val -from vspec.utils.idgen_utils import ( - get_node_identifier_bytes, - fnv1_32_hash, - get_all_keys_values, -) -import yaml +from vspec.utils.idgen_utils import (fnv1_32_hash, get_all_keys_values, + get_node_identifier_bytes) def add_arguments(parser: argparse.ArgumentParser) -> None: @@ -42,39 +41,53 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: default=False, help="For pytests and pipelines you can skip the export of the vspec file.", ) + parser.add_argument( + "--strict-mode", + action="store_true", + help="Strict mode means that the generation of static UIDs is case-sensitive.", + ) -def generate_split_id(node: VSSNode, id_counter: int) -> Tuple[str, int]: +def generate_split_id( + node: VSSNode, id_counter: int, strict_mode: bool +) -> Tuple[str, int]: """Generates static UIDs using 4-byte FNV-1 hash. @param node: VSSNode that we want to generate a static UID for @param id_counter: consecutive numbers counter for amount of nodes + @param strict_mode: strict mode means case sensitivity for static UID generation @return: tuple of hashed string and id counter """ + if hasattr(node, "fka") and node.fka: + name = node.fka[0] if isinstance(node.fka, list) else node.fka + else: + name = node.qualified_name() identifier = get_node_identifier_bytes( - node.qualified_name(), + name, node.data_type_str, node.type.value, node.get_unit(), node.allowed, node.min, node.max, + strict_mode, ) hashed_str = format(fnv1_32_hash(identifier), "08X") return hashed_str, id_counter + 1 -def export_node(yaml_dict, node, id_counter) -> Tuple[int, int]: +def export_node(yaml_dict, node, id_counter, strict_mode: bool) -> Tuple[int, int]: """Recursive function to export the full tree to a dict @param yaml_dict: the to be exported dict @param node: parent node of the tree @param id_counter: counter for amount of ids + @param strict_mode: strict mode means case sensitivity for static UID generation @return: id_counter, id_counter """ - node_id, id_counter = generate_split_id(node, id_counter) + node_id, id_counter = generate_split_id(node, id_counter, strict_mode) # check for hash duplicates for key, value in get_all_keys_values(yaml_dict): @@ -114,11 +127,7 @@ def export_node(yaml_dict, node, id_counter) -> Tuple[int, int]: yaml_dict[node_path]["deprecation"] = node.deprecation for child in node.children: - id_counter, id_counter = export_node( - yaml_dict, - child, - id_counter, - ) + id_counter, id_counter = export_node(yaml_dict, child, id_counter, strict_mode) return id_counter, id_counter @@ -134,7 +143,9 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid): id_counter: int = 0 signals_yaml_dict: Dict[str, str] = {} # Use str for ID values - id_counter, _ = export_node(signals_yaml_dict, signal_root, id_counter) + id_counter, _ = export_node( + signals_yaml_dict, signal_root, id_counter, config.strict_mode + ) if config.validate_static_uid: logging.info(