From 5dff6ad4dc817622dbcdac0662a72346eafbe319 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 26 Jul 2024 14:56:40 -0400 Subject: [PATCH] Add global types (structs/enums/bitmaps) to matter file parsing (#34515) * Make the IDL parser parse global types * Restyle * Parsing ok, started adding compatibility unit tests (which fail) * backwards compatibility tests pass now * Start implementing global type merging. Still needs unit tests * Prepare matter tests * A first test for global parsing * Fix bugs and have a working unit test for the non-recursive bit * Recursive unit test passes * Fix lint * Fix lint * Add check for global type uniqueness * Restyle --------- Co-authored-by: Andrei Litvin --- .../matter_idl/backwards_compatibility.py | 6 + .../matter_idl/generators/idl/MatterIdl.jinja | 60 +++- .../matter_idl/matter_grammar.lark | 2 +- .../matter_idl/matter_idl_parser.py | 118 +++++++- .../matter_idl/matter_idl_types.py | 8 + .../test_backwards_compatibility.py | 77 +++++ .../matter_idl/test_data_model_xml.py | 1 + .../matter_idl/test_matter_idl_parser.py | 271 ++++++++++++++++-- 8 files changed, 502 insertions(+), 41 deletions(-) diff --git a/scripts/py_matter_idl/matter_idl/backwards_compatibility.py b/scripts/py_matter_idl/matter_idl/backwards_compatibility.py index 04377100312756..e431b6227575c7 100644 --- a/scripts/py_matter_idl/matter_idl/backwards_compatibility.py +++ b/scripts/py_matter_idl/matter_idl/backwards_compatibility.py @@ -318,6 +318,12 @@ def check(self): self._check_cluster_list_compatible( self._original_idl.clusters, self._updated_idl.clusters) + self._check_enum_list_compatible( + "", self._original_idl.global_enums, self._updated_idl.global_enums) + self._check_bitmap_list_compatible( + "", self._original_idl.global_bitmaps, self._updated_idl.global_bitmaps) + self._check_struct_list_compatible( + "", self._original_idl.global_structs, self._updated_idl.global_structs) return self.compatible diff --git a/scripts/py_matter_idl/matter_idl/generators/idl/MatterIdl.jinja b/scripts/py_matter_idl/matter_idl/generators/idl/MatterIdl.jinja index 38553bb0b7f3ee..73be97dd3ae5cb 100644 --- a/scripts/py_matter_idl/matter_idl/generators/idl/MatterIdl.jinja +++ b/scripts/py_matter_idl/matter_idl/generators/idl/MatterIdl.jinja @@ -32,13 +32,65 @@ // This IDL was auto-generated from a parsed data structure -{% for cluster in idl.clusters %} +{% for enum in idl.global_enums %} +enum {{enum.name}} : {{ enum.base_type}} { + {% for entry in enum.entries %} + {{entry.name}} = {{entry.code}}; + {% endfor %} +} + +{% endfor %} + +{%- for bitmap in idl.global_bitmaps %} +bitmap {{bitmap.name}} : {{ bitmap.base_type}} { + {% for entry in bitmap.entries %} + {{entry.name}} = 0x{{"%X" | format(entry.code)}}; + {% endfor %} +} + +{% endfor %} + +{%- for s in idl.global_structs %} +{{render_struct(s)}} + +{% endfor %} + +{%- for cluster in idl.clusters %} {% if cluster.description %}/** {{cluster.description}} */ {% endif %} {{cluster.api_maturity | idltxt}}cluster {{cluster.name}} = {{cluster.code}} { revision {{cluster.revision}}; - {% for enum in cluster.enums %} + {% for enum in cluster.enums | selectattr("is_global")%} + /* GLOBAL: + enum {{enum.name}} : {{ enum.base_type}} { + {% for entry in enum.entries %} + {{entry.name}} = {{entry.code}}; + {% endfor %} + } + */ + + {% endfor %} + + {%- for bitmap in cluster.bitmaps | selectattr("is_global")%} + /* GLOBAL: + bitmap {{bitmap.name}} : {{ bitmap.base_type}} { + {% for entry in bitmap.entries %} + {{entry.name}} = 0x{{"%X" | format(entry.code)}}; + {% endfor %} + } + */ + + {% endfor %} + + {%- for s in cluster.structs | selectattr("is_global") %} + /* GLOBAL: + {{render_struct(s)}} + */ + + {% endfor %} + + {%- for enum in cluster.enums | rejectattr("is_global")%} enum {{enum.name}} : {{ enum.base_type}} { {% for entry in enum.entries %} {{entry.name}} = {{entry.code}}; @@ -47,7 +99,7 @@ {% endfor %} - {%- for bitmap in cluster.bitmaps %} + {%- for bitmap in cluster.bitmaps | rejectattr("is_global")%} bitmap {{bitmap.name}} : {{ bitmap.base_type}} { {% for entry in bitmap.entries %} {{entry.name}} = 0x{{"%X" | format(entry.code)}}; @@ -56,7 +108,7 @@ {% endfor %} - {%- for s in cluster.structs | rejectattr("tag") %} + {%- for s in cluster.structs | rejectattr("tag") | rejectattr("is_global") %} {{render_struct(s)}} {% endfor %} diff --git a/scripts/py_matter_idl/matter_idl/matter_grammar.lark b/scripts/py_matter_idl/matter_idl/matter_grammar.lark index a2838e1fa8eb18..4013c51bb52a59 100644 --- a/scripts/py_matter_idl/matter_idl/matter_grammar.lark +++ b/scripts/py_matter_idl/matter_idl/matter_grammar.lark @@ -116,7 +116,7 @@ POSITIVE_INTEGER: /\d+/ HEX_INTEGER: /0x[A-Fa-f0-9]+/ ID: /[a-zA-Z_][a-zA-Z0-9_]*/ -idl: (cluster|endpoint)* +idl: (struct|enum|bitmap|cluster|endpoint)* %import common.ESCAPED_STRING %import common.WS diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py index e33c0c7e872502..e071d008d1d726 100755 --- a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py @@ -1,8 +1,9 @@ #!/usr/bin/env python +import dataclasses import functools import logging -from typing import Dict, Optional +from typing import Dict, Optional, Set from lark import Lark from lark.lexer import Token @@ -504,15 +505,25 @@ def idl(self, items): clusters = [] endpoints = [] + global_bitmaps = [] + global_enums = [] + global_structs = [] + for item in items: if isinstance(item, Cluster): clusters.append(item) elif isinstance(item, Endpoint): endpoints.append(item) + elif isinstance(item, Enum): + global_enums.append(dataclasses.replace(item, is_global=True)) + elif isinstance(item, Bitmap): + global_bitmaps.append(dataclasses.replace(item, is_global=True)) + elif isinstance(item, Struct): + global_structs.append(dataclasses.replace(item, is_global=True)) else: raise Exception("UNKNOWN idl content item: %r" % item) - return Idl(clusters=clusters, endpoints=endpoints) + return Idl(clusters=clusters, endpoints=endpoints, global_bitmaps=global_bitmaps, global_enums=global_enums, global_structs=global_structs) def prefix_doc_comment(self): print("TODO: prefix") @@ -524,9 +535,92 @@ def c_comment(self, token: Token): self.doc_comments.append(PrefixCppDocComment(token)) +def _referenced_type_names(cluster: Cluster) -> Set[str]: + """ + Return the names of all data types referenced by the given cluster. + """ + types = set() + for s in cluster.structs: + for f in s.fields: + types.add(f.data_type.name) + + for e in cluster.events: + for f in e.fields: + types.add(f.data_type.name) + + for a in cluster.attributes: + types.add(a.definition.data_type.name) + + return types + + +class GlobalMapping: + """ + Maintains global type mapping from an IDL + """ + + def __init__(self, idl: Idl): + self.bitmap_map = {b.name: b for b in idl.global_bitmaps} + self.enum_map = {e.name: e for e in idl.global_enums} + self.struct_map = {s.name: s for s in idl.global_structs} + + self.global_types = set(self.bitmap_map.keys()).union(set(self.enum_map.keys())).union(set(self.struct_map.keys())) + + # Spec does not enforce unique naming in bitmap/enum/struct, however in practice + # if we have both enum Foo and bitmap Foo for example, it would be impossible + # to disambiguate `attribute Foo foo = 1` for the actual type we want. + # + # As a result, we do not try to namespace this and just error out + if len(self.global_types) != len(self.bitmap_map) + len(self.enum_map) + len(self.struct_map): + raise ValueError("Global type names are not unique.") + + def merge_global_types_into_cluster(self, cluster: Cluster) -> Cluster: + """ + Merges all referenced global types (bitmaps/enums/structs) into the cluster types. + This happens recursively. + """ + global_types_added = set() + + changed = True + while changed: + changed = False + for type_name in _referenced_type_names(cluster): + if type_name not in self.global_types: + continue # not a global type name + + if type_name in global_types_added: + continue # already added + + # check if this is a global type + if type_name in self.bitmap_map: + global_types_added.add(type_name) + changed = True + cluster.bitmaps.append(self.bitmap_map[type_name]) + elif type_name in self.enum_map: + global_types_added.add(type_name) + changed = True + cluster.enums.append(self.enum_map[type_name]) + elif type_name in self.struct_map: + global_types_added.add(type_name) + changed = True + cluster.structs.append(self.struct_map[type_name]) + + return cluster + + +def _merge_global_types_into_clusters(idl: Idl) -> Idl: + """ + Adds bitmaps/enums/structs from idl.global_* into clusters as long as + clusters reference those type names + """ + mapping = GlobalMapping(idl) + return dataclasses.replace(idl, clusters=[mapping.merge_global_types_into_cluster(cluster) for cluster in idl.clusters]) + + class ParserWithLines: - def __init__(self, skip_meta: bool): + def __init__(self, skip_meta: bool, merge_globals: bool): self.transformer = MatterIdlTransformer(skip_meta) + self.merge_globals = merge_globals # NOTE: LALR parser is fast. While Earley could parse more ambigous grammars, # earley is much slower: @@ -572,14 +666,28 @@ def parse(self, file: str, file_name: Optional[str] = None): for comment in self.transformer.doc_comments: comment.appply_to_idl(idl, file) + if self.merge_globals: + idl = _merge_global_types_into_clusters(idl) + return idl -def CreateParser(skip_meta: bool = False): +def CreateParser(skip_meta: bool = False, merge_globals=True): """ Generates a parser that will process a ".matter" file into a IDL + + Arguments: + skip_meta - do not add metadata (line position) for items. Metadata is + useful for error reporting, however it does not work well + for unit test comparisons + + merge_globals - places global items (enums/bitmaps/structs) into any + clusters that reference them, so that cluster types + are self-sufficient. Useful as a backwards-compatible + code generation if global definitions are not supported. + """ - return ParserWithLines(skip_meta) + return ParserWithLines(skip_meta, merge_globals) if __name__ == '__main__': diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_types.py b/scripts/py_matter_idl/matter_idl/matter_idl_types.py index 3515e5e655bc7a..d4a2195457ed60 100644 --- a/scripts/py_matter_idl/matter_idl/matter_idl_types.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_types.py @@ -162,6 +162,7 @@ class Struct: code: Optional[int] = None # for responses only qualities: StructQuality = StructQuality.NONE api_maturity: ApiMaturity = ApiMaturity.STABLE + is_global: bool = False @dataclass @@ -193,6 +194,7 @@ class Enum: base_type: str entries: List[ConstantEntry] api_maturity: ApiMaturity = ApiMaturity.STABLE + is_global: bool = False @dataclass @@ -201,6 +203,7 @@ class Bitmap: base_type: str entries: List[ConstantEntry] api_maturity: ApiMaturity = ApiMaturity.STABLE + is_global: bool = False @dataclass @@ -290,5 +293,10 @@ class Idl: clusters: List[Cluster] = field(default_factory=list) endpoints: List[Endpoint] = field(default_factory=list) + # Global types + global_bitmaps: List[Bitmap] = field(default_factory=list) + global_enums: List[Enum] = field(default_factory=list) + global_structs: List[Struct] = field(default_factory=list) + # IDL file name is available only if parsing provides a file name parse_file_name: Optional[str] = field(default=None) diff --git a/scripts/py_matter_idl/matter_idl/test_backwards_compatibility.py b/scripts/py_matter_idl/matter_idl/test_backwards_compatibility.py index e94e79abd5fce5..e2657dd2e5d93d 100755 --- a/scripts/py_matter_idl/matter_idl/test_backwards_compatibility.py +++ b/scripts/py_matter_idl/matter_idl/test_backwards_compatibility.py @@ -90,6 +90,83 @@ def ValidateUpdate(self, name: str, old: str, new: str, flags: Compatibility): with self.subTest(direction="OLD-to-OLD"): self._AssumeCompatiblity(old, old, old_idl, old_idl, True) + def test_global_enums_delete(self): + self.ValidateUpdate( + "delete a top level enum", + "enum A: ENUM8{} enum B: ENUM8{}", + "enum A: ENUM8{}", + Compatibility.FORWARD_FAIL) + + def test_global_enums_change(self): + self.ValidateUpdate( + "change an enum type", + "enum A: ENUM8{}", + "enum A: ENUM16{}", + Compatibility.FORWARD_FAIL | Compatibility.BACKWARD_FAIL) + + def test_global_enums_add_remove(self): + self.ValidateUpdate( + "Adding enum values is ok, removing values is not", + "enum A: ENUM8 {A = 1; B = 2;}", + "enum A: ENUM8 {A = 1; }", + Compatibility.FORWARD_FAIL) + + def test_global_enums_code(self): + self.ValidateUpdate( + "Switching enum codes is never ok", + "enum A: ENUM8 {A = 1; B = 2; }", + "enum A: ENUM8 {A = 1; B = 3; }", + Compatibility.FORWARD_FAIL | Compatibility.BACKWARD_FAIL) + + def test_global_bitmaps_delete(self): + self.ValidateUpdate( + "Deleting a global bitmap", + "bitmap A: BITMAP8{} bitmap B: BITMAP8{}", + "bitmap A: BITMAP8{} ", + Compatibility.FORWARD_FAIL) + + def test_global_bitmaps_type(self): + self.ValidateUpdate( + "Changing a bitmap type is never ok", + " bitmap A: BITMAP8{}", + " bitmap A: BITMAP16{}", + Compatibility.FORWARD_FAIL | Compatibility.BACKWARD_FAIL) + + def test_global_bitmap_values(self): + self.ValidateUpdate( + "Adding bitmap values is ok, removing values is not", + " bitmap A: BITMAP8 { kA = 0x01; kB = 0x02; } ", + " bitmap A: BITMAP8 { kA = 0x01; } ", + Compatibility.FORWARD_FAIL) + + def test_global_struct_removal(self): + self.ValidateUpdate( + "Structure removal is not ok, but adding is ok", + "struct Foo {} struct Bar {} ", + "struct Foo {} ", + Compatibility.FORWARD_FAIL) + + def test_global_struct_content_type_change(self): + self.ValidateUpdate( + "Changing structure data types is never ok", + "struct Foo { int32u x = 1; }", + "struct Foo { int64u x = 1; }", + Compatibility.FORWARD_FAIL | Compatibility.BACKWARD_FAIL) + + def test_global_struct_content_rename_reorder(self): + self.ValidateUpdate( + "Structure content renames and reorder is ok.", + "struct Foo { int32u x = 1; int8u y = 2; }", + "struct Foo { int8u a = 2; int32u y = 1; }", + Compatibility.ALL_OK) + + def test_global_struct_content_add_remove(self): + self.ValidateUpdate( + "Structure content change is not ok.", + "struct Foo { int32u x = 1; }", + "struct Foo { int32u x = 1; int8u y = 2; }", + Compatibility.FORWARD_FAIL | Compatibility.BACKWARD_FAIL) + def test_basic_clusters_enum(self): self.ValidateUpdate( "Adding an enum is ok. Also validates code formatting", diff --git a/scripts/py_matter_idl/matter_idl/test_data_model_xml.py b/scripts/py_matter_idl/matter_idl/test_data_model_xml.py index 6bdb530f7cc9fc..7a606cc2ec9c6e 100755 --- a/scripts/py_matter_idl/matter_idl/test_data_model_xml.py +++ b/scripts/py_matter_idl/matter_idl/test_data_model_xml.py @@ -94,6 +94,7 @@ def assertIdlEqual(self, a: Idl, b: Idl): tofile='expected.matter', ) self.assertEqual(a, b, '\n' + ''.join(delta)) + self.fail("IDLs are not equal (above delta should have failed)") def testBasicInput(self): diff --git a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py index e5de70e940eb19..ab79c279b0697f 100755 --- a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from difflib import unified_diff + try: from matter_idl.matter_idl_parser import CreateParser except ModuleNotFoundError: @@ -25,19 +27,62 @@ from matter_idl.matter_idl_parser import CreateParser import unittest +from typing import Optional +from matter_idl.generators import GeneratorStorage +from matter_idl.generators.idl import IdlGenerator from matter_idl.matter_idl_types import (AccessPrivilege, ApiMaturity, Attribute, AttributeInstantiation, AttributeQuality, AttributeStorage, Bitmap, Cluster, Command, CommandInstantiation, CommandQuality, ConstantEntry, DataType, DeviceType, Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, ParseMetaData, ServerClusterInstantiation, Struct, StructTag) -def parseText(txt, skip_meta=True): - return CreateParser(skip_meta=skip_meta).parse(txt) +class GeneratorContentStorage(GeneratorStorage): + def __init__(self): + super().__init__() + self.content: Optional[str] = None + + def get_existing_data(self, relative_path: str): + # Force re-generation each time + return None + + def write_new_data(self, relative_path: str, content: str): + if self.content: + raise Exception( + "Unexpected extra data: single file generation expected") + self.content = content + + +def RenderAsIdlTxt(idl: Idl) -> str: + storage = GeneratorContentStorage() + IdlGenerator(storage=storage, idl=idl).render(dry_run=False) + return storage.content or "" + + +def parseText(txt, skip_meta=True, merge_globals=True): + return CreateParser(skip_meta=skip_meta, merge_globals=merge_globals).parse(txt) class TestParser(unittest.TestCase): + def assertIdlEqual(self, a: Idl, b: Idl): + if a == b: + # seems the same. This will just pass + self.assertEqual(a, b) + return + + # Not the same. Try to make a human readable diff: + a_txt = RenderAsIdlTxt(a) + b_txt = RenderAsIdlTxt(b) + + delta = unified_diff(a_txt.splitlines(keepends=True), + b_txt.splitlines(keepends=True), + fromfile='actual.matter', + tofile='expected.matter', + ) + self.assertEqual(a, b, '\n' + ''.join(delta)) + self.fail("IDLs are not equal (above delta should have failed)") + def test_skips_comments(self): actual = parseText(""" // this is a single line comment @@ -49,7 +94,7 @@ def test_skips_comments(self): """) expected = Idl() - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_attribute(self): actual = parseText(""" @@ -75,7 +120,7 @@ def test_cluster_attribute(self): data_type=DataType(name="int8s"), code=0xAB, name="isNullable", qualities=FieldQuality.NULLABLE)), ] )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_doc_comments(self): actual = parseText(""" @@ -96,12 +141,12 @@ def test_doc_comments(self): # meta_data may not match but is required for doc comments. Clean it up # Metadata parsing varies line/column, so only check doc comments - self.assertEqual( + self.assertIdlEqual( actual.clusters[0].description, "Documentation for MyCluster") - self.assertEqual( + self.assertIdlEqual( actual.clusters[1].description, "Documentation for MyCluster #2") self.assertIsNone(actual.clusters[1].commands[0].description) - self.assertEqual( + self.assertIdlEqual( actual.clusters[1].commands[1].description, "Some command doc comment") def test_sized_attribute(self): @@ -122,7 +167,7 @@ def test_sized_attribute(self): data_type=DataType(name="octet_string", max_length=33), code=2, name="attr2", is_list=True)), ] )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_timed_attributes(self): actual = parseText(""" @@ -148,7 +193,7 @@ def test_timed_attributes(self): data_type=DataType(name="octet_string", max_length=44), code=4, name="attr4", is_list=True)), ] )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_attribute_access(self): actual = parseText(""" @@ -190,7 +235,7 @@ def test_attribute_access(self): ), ] )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_commands(self): actual = parseText(""" @@ -232,7 +277,7 @@ def test_cluster_commands(self): qualities=CommandQuality.TIMED_INVOKE | CommandQuality.FABRIC_SCOPED), ], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_command_access(self): actual = parseText(""" @@ -268,7 +313,7 @@ def test_cluster_command_access(self): ), ], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_enum(self): actual = parseText(""" @@ -289,7 +334,7 @@ def test_cluster_enum(self): ConstantEntry(name="B", code=0x234), ])], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_event_field_api_maturity(self): actual = parseText(""" @@ -316,7 +361,7 @@ def test_event_field_api_maturity(self): ]), ], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_enum_constant_maturity(self): actual = parseText(""" @@ -341,7 +386,7 @@ def test_enum_constant_maturity(self): name="kInternal", code=0x345, api_maturity=ApiMaturity.INTERNAL), ])], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_bitmap_constant_maturity(self): actual = parseText(""" @@ -366,7 +411,7 @@ def test_bitmap_constant_maturity(self): name="kProvisional", code=0x4, api_maturity=ApiMaturity.PROVISIONAL), ])], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_struct_field_api_maturity(self): actual = parseText(""" @@ -393,7 +438,7 @@ def test_struct_field_api_maturity(self): ]), ], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_entry_maturity(self): actual = parseText(""" @@ -510,7 +555,7 @@ def test_cluster_entry_maturity(self): data_type=DataType(name="int32u"), code=31, name="rwForcedStable", is_list=True), api_maturity=ApiMaturity.STABLE), ] )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_bitmap(self): actual = parseText(""" @@ -531,7 +576,7 @@ def test_cluster_bitmap(self): ConstantEntry(name="kSecond", code=0x2), ])], )]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_events(self): actual = parseText(""" @@ -556,7 +601,7 @@ def test_cluster_events(self): Event(priority=EventPriority.DEBUG, name="GoodBye", code=2, fields=[]), ])]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_event_acl(self): actual = parseText(""" @@ -577,7 +622,7 @@ def test_cluster_event_acl(self): Event(priority=EventPriority.DEBUG, readacl=AccessPrivilege.ADMINISTER, name="AdminEvent", code=3, fields=[]), ])]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_fabric_sensitive_event(self): actual = parseText(""" @@ -598,7 +643,7 @@ def test_fabric_sensitive_event(self): Event(priority=EventPriority.DEBUG, readacl=AccessPrivilege.ADMINISTER, name="AdminEvent", code=3, fields=[], qualities=EventQuality.FABRIC_SENSITIVE), ])]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_parsing_metadata_for_cluster(self): actual = CreateParser(skip_meta=False).parse(""" @@ -614,7 +659,7 @@ def test_parsing_metadata_for_cluster(self): Cluster(parse_meta=ParseMetaData(line=5, column=4, start_pos=87), name="B", code=2), ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_multiple_clusters(self): actual = parseText(""" @@ -628,7 +673,7 @@ def test_multiple_clusters(self): Cluster(name="B", code=2), Cluster(name="C", code=3), ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_endpoints(self): actual = parseText(""" @@ -658,7 +703,7 @@ def test_endpoints(self): ], client_bindings=["Bar", "Test"],) ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_instantiation(self): actual = parseText(""" @@ -693,7 +738,7 @@ def test_cluster_instantiation(self): ], client_bindings=[],) ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_multi_endpoints(self): actual = parseText(""" @@ -709,7 +754,7 @@ def test_multi_endpoints(self): Endpoint(number=10), Endpoint(number=100), ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_cluster_api_maturity(self): actual = parseText(""" @@ -723,7 +768,171 @@ def test_cluster_api_maturity(self): Cluster(name="B", code=2, api_maturity=ApiMaturity.INTERNAL), Cluster(name="C", code=3), ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) + + def test_just_globals(self): + actual = parseText(""" + enum TestEnum : ENUM16 { A = 0x123; B = 0x234; } + bitmap TestBitmap : BITMAP32 { + kStable = 0x1; + internal kInternal = 0x2; + provisional kProvisional = 0x4; + } + struct TestStruct { + nullable int16u someStableMember = 0; + provisional nullable int16u someProvisionalMember = 1; + internal nullable int16u someInternalMember = 2; + } + """) + + expected = Idl( + global_enums=[ + Enum(name="TestEnum", base_type="ENUM16", + entries=[ + ConstantEntry(name="A", code=0x123), + ConstantEntry(name="B", code=0x234), + ], + is_global=True, + )], + global_bitmaps=[ + Bitmap(name="TestBitmap", base_type="BITMAP32", + entries=[ + ConstantEntry(name="kStable", code=0x1), + ConstantEntry( + name="kInternal", code=0x2, api_maturity=ApiMaturity.INTERNAL), + ConstantEntry( + name="kProvisional", code=0x4, api_maturity=ApiMaturity.PROVISIONAL), + ], + is_global=True, + )], + global_structs=[ + Struct(name="TestStruct", fields=[ + Field(name="someStableMember", code=0, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE), + Field(name="someProvisionalMember", code=1, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.PROVISIONAL), + Field(name="someInternalMember", code=2, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.INTERNAL), + + ], + is_global=True, + )], + ) + self.assertIdlEqual(actual, expected) + + def test_cluster_reference_globals(self): + actual = parseText(""" + enum TestEnum : ENUM16 {} + bitmap TestBitmap : BITMAP32 {} + struct TestStruct {} + + server cluster Foo = 1 { + info event BitmapEvent = 0 { + TestBitmap someBitmap = 0; + } + struct MyStruct { + nullable TestStruct subStruct = 0; + } + readonly attribute TestEnum enumAttribute = 1; + } + """) + + global_enum = Enum(name="TestEnum", base_type="ENUM16", entries=[], is_global=True) + global_bitmap = Bitmap(name="TestBitmap", base_type="BITMAP32", entries=[], is_global=True) + global_struct = Struct(name="TestStruct", fields=[], is_global=True) + expected = Idl( + global_enums=[global_enum], + global_bitmaps=[global_bitmap], + global_structs=[global_struct], + clusters=[ + Cluster( + name="Foo", + code=1, + enums=[global_enum], + bitmaps=[global_bitmap], + events=[ + Event(priority=EventPriority.INFO, + name="BitmapEvent", code=0, fields=[ + Field(data_type=DataType(name="TestBitmap"), + code=0, name="someBitmap"), + ]), + ], + structs=[ + Struct(name="MyStruct", fields=[ + Field(name="subStruct", code=0, data_type=DataType(name="TestStruct"), qualities=FieldQuality.NULLABLE), ], + ), + global_struct, + ], + attributes=[ + Attribute(qualities=AttributeQuality.READABLE, definition=Field( + data_type=DataType(name="TestEnum"), code=1, name="enumAttribute")), + ], + ) + ], + ) + self.assertIdlEqual(actual, expected) + + def test_cluster_reference_globals_recursive(self): + actual = parseText(""" + enum TestEnum : ENUM16 {} + bitmap TestBitmap : BITMAP32 {} + + struct TestStruct1 { + TestEnum enumField = 0; + } + + struct TestStruct2 { + TestStruct1 substruct = 0; + } + + struct TestStruct3 { + TestStruct2 substruct = 0; + TestBitmap bmp = 1; + } + + server cluster Foo = 1 { + attribute TestStruct3 structAttr = 1; + } + """) + + global_enum = Enum(name="TestEnum", base_type="ENUM16", entries=[], is_global=True) + global_bitmap = Bitmap(name="TestBitmap", base_type="BITMAP32", entries=[], is_global=True) + global_struct1 = Struct(name="TestStruct1", fields=[ + Field(name="enumField", code=0, data_type=DataType(name="TestEnum")), + + ], is_global=True) + global_struct2 = Struct(name="TestStruct2", fields=[ + Field(name="substruct", code=0, data_type=DataType(name="TestStruct1")), + + ], is_global=True) + global_struct3 = Struct(name="TestStruct3", fields=[ + Field(name="substruct", code=0, data_type=DataType(name="TestStruct2")), + Field(name="bmp", code=1, data_type=DataType(name="TestBitmap")), + ], is_global=True) + expected = Idl( + global_enums=[global_enum], + global_bitmaps=[global_bitmap], + global_structs=[global_struct1, global_struct2, global_struct3], + clusters=[ + Cluster( + name="Foo", + code=1, + enums=[global_enum], + bitmaps=[global_bitmap], + structs=[ + global_struct3, + global_struct2, + global_struct1, + ], + attributes=[ + Attribute( + qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, + definition=Field(data_type=DataType(name="TestStruct3"), code=1, name="structAttr")), + ], + ) + ], + ) + self.assertIdlEqual(actual, expected) def test_emits_events(self): actual = parseText(""" @@ -753,7 +962,7 @@ def test_emits_events(self): ]) ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_revision(self): actual = parseText(""" @@ -769,7 +978,7 @@ def test_revision(self): Cluster(name="C", code=3, revision=2), Cluster(name="D", code=4, revision=123), ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) def test_handle_commands(self): actual = parseText(""" @@ -801,7 +1010,7 @@ def test_handle_commands(self): ]) ]) - self.assertEqual(actual, expected) + self.assertIdlEqual(actual, expected) if __name__ == '__main__':