| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Tests for attribute_checker.py.""" |
| |
| import unittest |
| from compiler.front_end import attribute_checker |
| from compiler.front_end import glue |
| from compiler.util import error |
| from compiler.util import ir_pb2 |
| from compiler.util import ir_util |
| from compiler.util import test_util |
| |
| # These are not shared with attribute_checker.py because their values are part |
| # of the contract with back ends. |
| _BYTE_ORDER = "byte_order" |
| _FIXED_SIZE = "fixed_size_in_bits" |
| _IS_SIGNED = "is_signed" |
| _MAX_BITS = "maximum_bits" |
| |
| |
| def _make_ir_from_emb(emb_text, name="m.emb"): |
| ir, unused_debug_info, errors = glue.parse_emboss_file( |
| name, |
| test_util.dict_file_reader({name: emb_text}), |
| stop_before_step="normalize_and_verify") |
| assert not errors |
| return ir |
| |
| |
| class NormalizeIrTest(unittest.TestCase): |
| |
| def test_rejects_may_be_used_as_integer(self): |
| enum_ir = _make_ir_from_emb("enum Foo:\n" |
| " [may_be_used_as_integer: false]\n" |
| " VALUE = 1\n") |
| enum_type_ir = enum_ir.module[0].type[0] |
| self.assertEqual([[ |
| error.error( |
| "m.emb", enum_type_ir.attribute[0].name.source_location, |
| "Unknown attribute 'may_be_used_as_integer' on enum 'Foo'.") |
| ]], attribute_checker.normalize_and_verify(enum_ir)) |
| |
| def test_adds_fixed_size_attribute_to_struct(self): |
| # field2 is intentionally after field3, in order to trigger certain code |
| # paths in attribute_checker.py. |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+2] UInt field1\n" |
| " 4 [+4] UInt field2\n" |
| " 2 [+2] UInt field3\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| _FIXED_SIZE) |
| self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| self.assertEqual(struct_ir.module[0].type[0].source_location, |
| size_attr.source_location) |
| |
| def test_adds_fixed_size_attribute_to_struct_with_virtual_field(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+2] UInt field1\n" |
| " let field2 = field1\n" |
| " 2 [+2] UInt field3\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| _FIXED_SIZE) |
| self.assertEqual(32, ir_util.constant_value(size_attr.expression)) |
| self.assertEqual(struct_ir.module[0].type[0].source_location, |
| size_attr.source_location) |
| |
| def test_adds_fixed_size_attribute_to_anonymous_bits(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+4] bits:\n" |
| " 0 [+8] UInt field\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| _FIXED_SIZE) |
| self.assertEqual(32, ir_util.constant_value(size_attr.expression)) |
| bits_size_attr = ir_util.get_attribute( |
| struct_ir.module[0].type[0].subtype[0].attribute, _FIXED_SIZE) |
| self.assertEqual(8, ir_util.constant_value(bits_size_attr.expression)) |
| self.assertEqual(struct_ir.module[0].type[0].source_location, |
| size_attr.source_location) |
| |
| def test_does_not_add_fixed_size_attribute_to_variable_size_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+4] UInt n\n" |
| " 4 [+n] UInt:8[] payload\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| self.assertIsNone(ir_util.get_attribute( |
| struct_ir.module[0].type[0].attribute, _FIXED_SIZE)) |
| |
| def test_accepts_correct_fixed_size_and_size_attributes_on_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: 64]\n" |
| " 0 [+2] UInt field1\n" |
| " 2 [+2] UInt field2\n" |
| " 4 [+4] UInt field3\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| _FIXED_SIZE) |
| self.assertTrue(size_attr) |
| self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| |
| def test_accepts_correct_size_attribute_on_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: 64]\n" |
| " 0 [+2] UInt field1\n" |
| " 4 [+4] UInt field3\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| _FIXED_SIZE) |
| self.assertTrue(size_attr.expression) |
| self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| |
| def test_rejects_incorrect_fixed_size_attribute_on_variable_size_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: 8]\n" |
| " 0 [+4] UInt n\n" |
| " 4 [+n] UInt:8[] payload\n") |
| struct_type_ir = struct_ir.module[0].type[0] |
| self.assertEqual([[error.error( |
| "m.emb", struct_type_ir.attribute[0].value.source_location, |
| "Struct is marked as fixed size, but contains variable-location " |
| "fields.")]], attribute_checker.normalize_and_verify(struct_ir)) |
| |
| def test_rejects_size_attribute_with_wrong_large_value_on_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: 80]\n" |
| " 0 [+2] UInt field1\n" |
| " 2 [+2] UInt field2\n" |
| " 4 [+4] UInt field3\n") |
| struct_type_ir = struct_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error("m.emb", struct_type_ir.attribute[0].value.source_location, |
| "Struct is 64 bits, but is marked as 80 bits.")] |
| ], attribute_checker.normalize_and_verify(struct_ir)) |
| |
| def test_rejects_size_attribute_with_wrong_small_value_on_struct(self): |
| struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: 40]\n" |
| " 0 [+2] UInt field1\n" |
| " 2 [+2] UInt field2\n" |
| " 4 [+4] UInt field3\n") |
| struct_type_ir = struct_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error("m.emb", struct_type_ir.attribute[0].value.source_location, |
| "Struct is 64 bits, but is marked as 40 bits.")] |
| ], attribute_checker.normalize_and_verify(struct_ir)) |
| |
| def test_accepts_variable_size_external(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [addressable_unit_size: 1]\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_accepts_fixed_size_external(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [fixed_size_in_bits: 32]\n" |
| " [addressable_unit_size: 1]\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_rejects_external_with_no_addressable_unit_size_attribute(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [is_integer: false]\n") |
| external_type_ir = external_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error( |
| "m.emb", external_type_ir.source_location, |
| "Expected 'addressable_unit_size' attribute for external type.")] |
| ], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_rejects_is_integer_with_non_constant_value(self): |
| external_ir = _make_ir_from_emb( |
| "external Foo:\n" |
| " [is_integer: $static_size_in_bits == 1]\n" |
| " [addressable_unit_size: 1]\n") |
| external_type_ir = external_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error( |
| "m.emb", external_type_ir.attribute[0].value.source_location, |
| "Attribute 'is_integer' must have a constant boolean value.")] |
| ], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_rejects_addressable_unit_size_with_non_constant_value(self): |
| external_ir = _make_ir_from_emb( |
| "external Foo:\n" |
| " [is_integer: true]\n" |
| " [addressable_unit_size: $static_size_in_bits]\n") |
| external_type_ir = external_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error( |
| "m.emb", external_type_ir.attribute[1].value.source_location, |
| "Attribute 'addressable_unit_size' must have a constant value.")] |
| ], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_rejects_external_with_wrong_addressable_unit_size_attribute(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [addressable_unit_size: 4]\n") |
| external_type_ir = external_ir.module[0].type[0] |
| self.assertEqual([ |
| [error.error( |
| "m.emb", external_type_ir.source_location, |
| "Only values '1' (bit) and '8' (byte) are allowed for the " |
| "'addressable_unit_size' attribute")] |
| ], attribute_checker.normalize_and_verify(external_ir)) |
| |
| def test_rejects_duplicate_attribute(self): |
| ir = _make_ir_from_emb("external Foo:\n" |
| " [is_integer: true]\n" |
| " [is_integer: true]\n") |
| self.assertEqual([[ |
| error.error("m.emb", ir.module[0].type[0].attribute[1].source_location, |
| "Duplicate attribute 'is_integer'."), |
| error.note("m.emb", ir.module[0].type[0].attribute[0].source_location, |
| "Original attribute"), |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_duplicate_default_attribute(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| '[$default byte_order: "LittleEndian"]\n') |
| self.assertEqual( |
| [[ |
| error.error("m.emb", ir.module[0].attribute[1].source_location, |
| "Duplicate attribute 'byte_order'."), |
| error.note("m.emb", ir.module[0].attribute[0].source_location, |
| "Original attribute"), |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_unknown_attribute(self): |
| ir = _make_ir_from_emb("[gibberish: true]\n") |
| attr = ir.module[0].attribute[0] |
| self.assertEqual([[ |
| error.error("m.emb", attr.name.source_location, |
| "Unknown attribute 'gibberish' on module 'm.emb'.") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_non_constant_attribute(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " [fixed_size_in_bits: field1]\n" |
| " 0 [+2] UInt field1\n") |
| attr = ir.module[0].type[0].attribute[0] |
| self.assertEqual( |
| [[ |
| error.error( |
| "m.emb", attr.value.source_location, |
| "Attribute 'fixed_size_in_bits' must have a constant value.") |
| ]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_attribute_missing_required_back_end_specifier(self): |
| ir = _make_ir_from_emb('[namespace: "abc"]\n') |
| attr = ir.module[0].attribute[0] |
| self.assertEqual([[ |
| error.error("m.emb", attr.name.source_location, |
| "Unknown attribute 'namespace' on module 'm.emb'.") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_accepts_attribute_with_default_known_back_end_specifier(self): |
| ir = _make_ir_from_emb('[(cpp) namespace: "abc"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_attribute_with_specified_back_end_specifier(self): |
| ir = _make_ir_from_emb('[(c) namespace: "abc"]\n' |
| '[expected_back_ends: "c, cpp"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_cpp_backend_attribute_when_not_in_expected_back_ends(self): |
| ir = _make_ir_from_emb('[(cpp) namespace: "abc"]\n' |
| '[expected_back_ends: "c"]\n') |
| attr = ir.module[0].attribute[0] |
| self.maxDiff = 200000 |
| self.assertEqual([[ |
| error.error( |
| "m.emb", attr.back_end.source_location, |
| "Back end specifier 'cpp' does not match any expected back end " |
| "specifier for this file: 'c'. Add or update the " |
| "'[expected_back_ends: \"c, cpp\"]' attribute at the file level if " |
| "this back end specifier is intentional.") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_expected_back_ends_with_bad_back_end(self): |
| ir = _make_ir_from_emb('[expected_back_ends: "c++"]\n') |
| attr = ir.module[0].attribute[0] |
| self.assertEqual([[ |
| error.error( |
| "m.emb", attr.value.source_location, |
| "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| "back end specifiers (like \"cpp, proto\")), not \"c++\".") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_expected_back_ends_with_no_comma(self): |
| ir = _make_ir_from_emb('[expected_back_ends: "cpp z"]\n') |
| attr = ir.module[0].attribute[0] |
| self.assertEqual([[ |
| error.error( |
| "m.emb", attr.value.source_location, |
| "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| "back end specifiers (like \"cpp, proto\")), not \"cpp z\".") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_expected_back_ends_with_extra_commas(self): |
| ir = _make_ir_from_emb('[expected_back_ends: "cpp,,z"]\n') |
| attr = ir.module[0].attribute[0] |
| self.assertEqual([[ |
| error.error( |
| "m.emb", attr.value.source_location, |
| "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| "back end specifiers (like \"cpp, proto\")), not \"cpp,,z\".") |
| ]], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_accepts_empty_expected_back_ends(self): |
| ir = _make_ir_from_emb('[expected_back_ends: ""]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_adds_byte_order_attributes_from_default(self): |
| ir = _make_ir_from_emb('[$default byte_order: "BigEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+2] UInt bar\n" |
| " 2 [+2] UInt baz\n" |
| ' [byte_order: "LittleEndian"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| byte_order_attr = ir_util.get_attribute( |
| ir.module[0].type[0].structure.field[0].attribute, _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("BigEndian", byte_order_attr.string_constant.text) |
| byte_order_attr = ir_util.get_attribute( |
| ir.module[0].type[0].structure.field[1].attribute, _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| |
| def test_adds_null_byte_order_attributes(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+1] UInt bar\n" |
| " 1 [+1] UInt baz\n" |
| ' [byte_order: "LittleEndian"]\n' |
| " 2 [+2] UInt:8[] baseball\n" |
| " 4 [+2] UInt:8[] bat\n" |
| ' [byte_order: "LittleEndian"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| structure = ir.module[0].type[0].structure |
| byte_order_attr = ir_util.get_attribute( |
| structure.field[0].attribute, _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("Null", byte_order_attr.string_constant.text) |
| self.assertEqual(structure.field[0].source_location, |
| byte_order_attr.source_location) |
| byte_order_attr = ir_util.get_attribute(structure.field[1].attribute, |
| _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| byte_order_attr = ir_util.get_attribute(structure.field[2].attribute, |
| _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("Null", byte_order_attr.string_constant.text) |
| self.assertEqual(structure.field[2].source_location, |
| byte_order_attr.source_location) |
| byte_order_attr = ir_util.get_attribute(structure.field[3].attribute, |
| _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| |
| def test_disallows_default_byte_order_on_field(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+2] UInt bar\n" |
| ' [$default byte_order: "LittleEndian"]\n') |
| default_byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", default_byte_order.name.source_location, |
| "Attribute 'byte_order' may not be defaulted on struct field 'bar'." |
| )]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_default_byte_order_on_bits(self): |
| ir = _make_ir_from_emb("bits Foo:\n" |
| ' [$default byte_order: "LittleEndian"]\n' |
| " 0 [+2] UInt bar\n") |
| default_byte_order = ir.module[0].type[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", default_byte_order.name.source_location, |
| "Attribute 'byte_order' may not be defaulted on bits 'Foo'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_default_byte_order_on_enum(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| ' [$default byte_order: "LittleEndian"]\n' |
| " BAR = 1\n") |
| default_byte_order = ir.module[0].type[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", default_byte_order.name.source_location, |
| "Attribute 'byte_order' may not be defaulted on enum 'Foo'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_adds_byte_order_from_scoped_default(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| ' [$default byte_order: "BigEndian"]\n' |
| " 0 [+2] UInt bar\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| byte_order_attr = ir_util.get_attribute( |
| ir.module[0].type[0].structure.field[0].attribute, _BYTE_ORDER) |
| self.assertTrue(byte_order_attr.HasField("string_constant")) |
| self.assertEqual("BigEndian", byte_order_attr.string_constant.text) |
| |
| def test_disallows_unknown_byte_order(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+2] UInt bar\n" |
| ' [byte_order: "NoEndian"]\n') |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'byte_order' must be 'BigEndian' or 'LittleEndian' or " |
| "'Null'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_unknown_default_byte_order(self): |
| ir = _make_ir_from_emb('[$default byte_order: "NoEndian"]\n') |
| default_byte_order = ir.module[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", default_byte_order.value.source_location, |
| "Attribute 'byte_order' must be 'BigEndian' or 'LittleEndian' or " |
| "'Null'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_byte_order_on_non_byte_order_dependent_fields(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| ' [$default byte_order: "LittleEndian"]\n' |
| " 0 [+2] UInt uint\n" |
| "struct Bar:\n" |
| " 0 [+2] Foo foo\n" |
| ' [byte_order: "LittleEndian"]\n') |
| byte_order = ir.module[0].type[1].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'byte_order' not allowed on field which is not byte " |
| "order dependent.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_byte_order_on_virtual_field(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " let x = 10\n" |
| ' [byte_order: "LittleEndian"]\n') |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.name.source_location, |
| "Unknown attribute 'byte_order' on virtual struct field 'x'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_null_byte_order_on_multibyte_fields(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+2] UInt uint\n" |
| ' [byte_order: "Null"]\n') |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'byte_order' may only be 'Null' for one-byte fields.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_null_byte_order_on_multibyte_array_elements(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+4] UInt:16[] uint\n" |
| ' [byte_order: "Null"]\n') |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'byte_order' may only be 'Null' for one-byte fields.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_requires_byte_order_on_byte_order_dependent_fields(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+2] UInt uint\n") |
| field = ir.module[0].type[0].structure.field[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", field.source_location, |
| "Attribute 'byte_order' required on field which is byte order " |
| "dependent.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_unknown_text_output_attribute(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+2] UInt bar\n" |
| ' [text_output: "None"]\n') |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'text_output' must be 'Emit' or 'Skip'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_disallows_non_string_text_output_attribute(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+2] UInt bar\n" |
| " [text_output: 0]\n") |
| byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", byte_order.value.source_location, |
| "Attribute 'text_output' must be 'Emit' or 'Skip'.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_allows_skip_text_output_attribute_on_physical_field(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+1] UInt bar\n" |
| ' [text_output: "Skip"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_allows_skip_text_output_attribute_on_virtual_field(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " let x = 10\n" |
| ' [text_output: "Skip"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_allows_emit_text_output_attribute_on_physical_field(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+1] UInt bar\n" |
| ' [text_output: "Emit"]\n') |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| |
| def test_adds_bit_addressable_unit_to_external(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [addressable_unit_size: 1]\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| self.assertEqual(ir_pb2.TypeDefinition.BIT, |
| external_ir.module[0].type[0].addressable_unit) |
| |
| def test_adds_byte_addressable_unit_to_external(self): |
| external_ir = _make_ir_from_emb("external Foo:\n" |
| " [addressable_unit_size: 8]\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| self.assertEqual(ir_pb2.TypeDefinition.BYTE, |
| external_ir.module[0].type[0].addressable_unit) |
| |
| def test_rejects_requires_using_array(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+4] UInt:8[] array\n" |
| " [requires: this]\n") |
| field_ir = ir.module[0].type[0].structure.field[0] |
| self.assertEqual( |
| [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| "Attribute 'requires' must have a boolean value.")]], |
| attribute_checker.normalize_and_verify(ir)) |
| |
| def test_rejects_requires_on_array(self): |
| ir = _make_ir_from_emb("struct Foo:\n" |
| " 0 [+4] UInt:8[] array\n" |
| " [requires: false]\n") |
| field_ir = ir.module[0].type[0].structure.field[0] |
| self.assertEqual( |
| [[ |
| error.error("m.emb", field_ir.attribute[0].value.source_location, |
| "Attribute 'requires' is only allowed on integer, " |
| "enumeration, or boolean fields, not arrays."), |
| error.note("m.emb", field_ir.type.source_location, |
| "Field type."), |
| ]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| def test_rejects_requires_on_struct(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+4] Bar bar\n" |
| " [requires: false]\n" |
| "struct Bar:\n" |
| " 0 [+4] UInt uint\n") |
| field_ir = ir.module[0].type[0].structure.field[0] |
| self.assertEqual( |
| [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| "Attribute 'requires' is only allowed on integer, " |
| "enumeration, or boolean fields.")]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| def test_rejects_requires_on_float(self): |
| ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| "struct Foo:\n" |
| " 0 [+4] Float float\n" |
| " [requires: false]\n") |
| field_ir = ir.module[0].type[0].structure.field[0] |
| self.assertEqual( |
| [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| "Attribute 'requires' is only allowed on integer, " |
| "enumeration, or boolean fields.")]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| def test_adds_false_is_signed_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " ZERO = 0\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| enum = ir.module[0].type[0] |
| is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| self.assertFalse(is_signed_attr.expression.boolean_constant.value) |
| |
| def test_leaves_is_signed_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " [is_signed: true]\n" |
| " ZERO = 0\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| enum = ir.module[0].type[0] |
| is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| self.assertTrue(is_signed_attr.expression.boolean_constant.value) |
| |
| def test_adds_true_is_signed_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " NEGATIVE_ONE = -1\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| enum = ir.module[0].type[0] |
| is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| self.assertTrue(is_signed_attr.expression.boolean_constant.value) |
| |
| def test_adds_max_bits_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " ZERO = 0\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| enum = ir.module[0].type[0] |
| max_bits_attr = ir_util.get_attribute(enum.attribute, _MAX_BITS) |
| self.assertTrue(max_bits_attr.expression.HasField("constant")) |
| self.assertEqual("64", max_bits_attr.expression.constant.value) |
| |
| def test_leaves_max_bits_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " [maximum_bits: 32]\n" |
| " ZERO = 0\n") |
| self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| enum = ir.module[0].type[0] |
| max_bits_attr = ir_util.get_attribute(enum.attribute, _MAX_BITS) |
| self.assertTrue(max_bits_attr.expression.HasField("constant")) |
| self.assertEqual("32", max_bits_attr.expression.constant.value) |
| |
| def test_rejects_too_small_max_bits(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " [maximum_bits: 0]\n" |
| " ZERO = 0\n") |
| attribute_ir = ir.module[0].type[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", attribute_ir.value.source_location, |
| "'maximum_bits' on an 'enum' must be between 1 and 64.")]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| def test_rejects_too_large_max_bits(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " [maximum_bits: 65]\n" |
| " ZERO = 0\n") |
| attribute_ir = ir.module[0].type[0].attribute[0] |
| self.assertEqual( |
| [[error.error( |
| "m.emb", attribute_ir.value.source_location, |
| "'maximum_bits' on an 'enum' must be between 1 and 64.")]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| def test_rejects_unknown_enum_value_attribute(self): |
| ir = _make_ir_from_emb("enum Foo:\n" |
| " BAR = 0 \n" |
| " [bad_attr: true]\n") |
| attribute_ir = ir.module[0].type[0].enumeration.value[0].attribute[0] |
| self.assertNotEqual([], attribute_checker.normalize_and_verify(ir)) |
| self.assertEqual( |
| [[error.error( |
| "m.emb", attribute_ir.name.source_location, |
| "Unknown attribute 'bad_attr' on enum value 'BAR'.")]], |
| error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |