Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 1 | # Copyright 2019 Google LLC |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Tests for attribute_checker.py.""" |
| 16 | |
| 17 | import unittest |
reventlov | 6731fc4 | 2019-10-03 15:23:13 -0700 | [diff] [blame] | 18 | from compiler.front_end import attribute_checker |
| 19 | from compiler.front_end import glue |
reventlov | 6731fc4 | 2019-10-03 15:23:13 -0700 | [diff] [blame] | 20 | from compiler.util import error |
Eric Rahm | 12c5e84 | 2024-03-22 09:37:24 -0700 | [diff] [blame] | 21 | from compiler.util import ir_data |
reventlov | 6731fc4 | 2019-10-03 15:23:13 -0700 | [diff] [blame] | 22 | from compiler.util import ir_util |
Jason Graffius | 9191077 | 2023-05-18 12:43:52 -0400 | [diff] [blame] | 23 | from compiler.util import test_util |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 24 | |
| 25 | # These are not shared with attribute_checker.py because their values are part |
| 26 | # of the contract with back ends. |
| 27 | _BYTE_ORDER = "byte_order" |
| 28 | _FIXED_SIZE = "fixed_size_in_bits" |
reventlov | c50913d | 2022-04-18 15:28:36 -0700 | [diff] [blame] | 29 | _IS_SIGNED = "is_signed" |
| 30 | _MAX_BITS = "maximum_bits" |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 31 | |
| 32 | |
| 33 | def _make_ir_from_emb(emb_text, name="m.emb"): |
| 34 | ir, unused_debug_info, errors = glue.parse_emboss_file( |
| 35 | name, |
| 36 | test_util.dict_file_reader({name: emb_text}), |
| 37 | stop_before_step="normalize_and_verify") |
| 38 | assert not errors |
| 39 | return ir |
| 40 | |
| 41 | |
| 42 | class NormalizeIrTest(unittest.TestCase): |
| 43 | |
| 44 | def test_rejects_may_be_used_as_integer(self): |
| 45 | enum_ir = _make_ir_from_emb("enum Foo:\n" |
| 46 | " [may_be_used_as_integer: false]\n" |
| 47 | " VALUE = 1\n") |
| 48 | enum_type_ir = enum_ir.module[0].type[0] |
| 49 | self.assertEqual([[ |
| 50 | error.error( |
| 51 | "m.emb", enum_type_ir.attribute[0].name.source_location, |
| 52 | "Unknown attribute 'may_be_used_as_integer' on enum 'Foo'.") |
| 53 | ]], attribute_checker.normalize_and_verify(enum_ir)) |
| 54 | |
| 55 | def test_adds_fixed_size_attribute_to_struct(self): |
| 56 | # field2 is intentionally after field3, in order to trigger certain code |
| 57 | # paths in attribute_checker.py. |
| 58 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 59 | "struct Foo:\n" |
| 60 | " 0 [+2] UInt field1\n" |
| 61 | " 4 [+4] UInt field2\n" |
| 62 | " 2 [+2] UInt field3\n") |
| 63 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 64 | size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| 65 | _FIXED_SIZE) |
| 66 | self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| 67 | self.assertEqual(struct_ir.module[0].type[0].source_location, |
| 68 | size_attr.source_location) |
| 69 | |
| 70 | def test_adds_fixed_size_attribute_to_struct_with_virtual_field(self): |
| 71 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 72 | "struct Foo:\n" |
| 73 | " 0 [+2] UInt field1\n" |
| 74 | " let field2 = field1\n" |
| 75 | " 2 [+2] UInt field3\n") |
| 76 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 77 | size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| 78 | _FIXED_SIZE) |
| 79 | self.assertEqual(32, ir_util.constant_value(size_attr.expression)) |
| 80 | self.assertEqual(struct_ir.module[0].type[0].source_location, |
| 81 | size_attr.source_location) |
| 82 | |
| 83 | def test_adds_fixed_size_attribute_to_anonymous_bits(self): |
| 84 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 85 | "struct Foo:\n" |
| 86 | " 0 [+4] bits:\n" |
| 87 | " 0 [+8] UInt field\n") |
| 88 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 89 | size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| 90 | _FIXED_SIZE) |
| 91 | self.assertEqual(32, ir_util.constant_value(size_attr.expression)) |
| 92 | bits_size_attr = ir_util.get_attribute( |
| 93 | struct_ir.module[0].type[0].subtype[0].attribute, _FIXED_SIZE) |
| 94 | self.assertEqual(8, ir_util.constant_value(bits_size_attr.expression)) |
| 95 | self.assertEqual(struct_ir.module[0].type[0].source_location, |
| 96 | size_attr.source_location) |
| 97 | |
| 98 | def test_does_not_add_fixed_size_attribute_to_variable_size_struct(self): |
| 99 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 100 | "struct Foo:\n" |
| 101 | " 0 [+4] UInt n\n" |
| 102 | " 4 [+n] UInt:8[] payload\n") |
| 103 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 104 | self.assertIsNone(ir_util.get_attribute( |
| 105 | struct_ir.module[0].type[0].attribute, _FIXED_SIZE)) |
| 106 | |
| 107 | def test_accepts_correct_fixed_size_and_size_attributes_on_struct(self): |
| 108 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 109 | "struct Foo:\n" |
| 110 | " [fixed_size_in_bits: 64]\n" |
| 111 | " 0 [+2] UInt field1\n" |
| 112 | " 2 [+2] UInt field2\n" |
| 113 | " 4 [+4] UInt field3\n") |
| 114 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 115 | size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| 116 | _FIXED_SIZE) |
| 117 | self.assertTrue(size_attr) |
| 118 | self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| 119 | |
| 120 | def test_accepts_correct_size_attribute_on_struct(self): |
| 121 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 122 | "struct Foo:\n" |
| 123 | " [fixed_size_in_bits: 64]\n" |
| 124 | " 0 [+2] UInt field1\n" |
| 125 | " 4 [+4] UInt field3\n") |
| 126 | self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) |
| 127 | size_attr = ir_util.get_attribute(struct_ir.module[0].type[0].attribute, |
| 128 | _FIXED_SIZE) |
| 129 | self.assertTrue(size_attr.expression) |
| 130 | self.assertEqual(64, ir_util.constant_value(size_attr.expression)) |
| 131 | |
| 132 | def test_rejects_incorrect_fixed_size_attribute_on_variable_size_struct(self): |
| 133 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 134 | "struct Foo:\n" |
| 135 | " [fixed_size_in_bits: 8]\n" |
| 136 | " 0 [+4] UInt n\n" |
| 137 | " 4 [+n] UInt:8[] payload\n") |
| 138 | struct_type_ir = struct_ir.module[0].type[0] |
| 139 | self.assertEqual([[error.error( |
| 140 | "m.emb", struct_type_ir.attribute[0].value.source_location, |
| 141 | "Struct is marked as fixed size, but contains variable-location " |
| 142 | "fields.")]], attribute_checker.normalize_and_verify(struct_ir)) |
| 143 | |
| 144 | def test_rejects_size_attribute_with_wrong_large_value_on_struct(self): |
| 145 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 146 | "struct Foo:\n" |
| 147 | " [fixed_size_in_bits: 80]\n" |
| 148 | " 0 [+2] UInt field1\n" |
| 149 | " 2 [+2] UInt field2\n" |
| 150 | " 4 [+4] UInt field3\n") |
| 151 | struct_type_ir = struct_ir.module[0].type[0] |
| 152 | self.assertEqual([ |
| 153 | [error.error("m.emb", struct_type_ir.attribute[0].value.source_location, |
| 154 | "Struct is 64 bits, but is marked as 80 bits.")] |
| 155 | ], attribute_checker.normalize_and_verify(struct_ir)) |
| 156 | |
| 157 | def test_rejects_size_attribute_with_wrong_small_value_on_struct(self): |
| 158 | struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 159 | "struct Foo:\n" |
| 160 | " [fixed_size_in_bits: 40]\n" |
| 161 | " 0 [+2] UInt field1\n" |
| 162 | " 2 [+2] UInt field2\n" |
| 163 | " 4 [+4] UInt field3\n") |
| 164 | struct_type_ir = struct_ir.module[0].type[0] |
| 165 | self.assertEqual([ |
| 166 | [error.error("m.emb", struct_type_ir.attribute[0].value.source_location, |
| 167 | "Struct is 64 bits, but is marked as 40 bits.")] |
| 168 | ], attribute_checker.normalize_and_verify(struct_ir)) |
| 169 | |
| 170 | def test_accepts_variable_size_external(self): |
| 171 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 172 | " [addressable_unit_size: 1]\n") |
| 173 | self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| 174 | |
| 175 | def test_accepts_fixed_size_external(self): |
| 176 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 177 | " [fixed_size_in_bits: 32]\n" |
| 178 | " [addressable_unit_size: 1]\n") |
| 179 | self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
| 180 | |
| 181 | def test_rejects_external_with_no_addressable_unit_size_attribute(self): |
| 182 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 183 | " [is_integer: false]\n") |
| 184 | external_type_ir = external_ir.module[0].type[0] |
| 185 | self.assertEqual([ |
| 186 | [error.error( |
| 187 | "m.emb", external_type_ir.source_location, |
| 188 | "Expected 'addressable_unit_size' attribute for external type.")] |
| 189 | ], attribute_checker.normalize_and_verify(external_ir)) |
| 190 | |
| 191 | def test_rejects_is_integer_with_non_constant_value(self): |
| 192 | external_ir = _make_ir_from_emb( |
| 193 | "external Foo:\n" |
| 194 | " [is_integer: $static_size_in_bits == 1]\n" |
| 195 | " [addressable_unit_size: 1]\n") |
| 196 | external_type_ir = external_ir.module[0].type[0] |
| 197 | self.assertEqual([ |
| 198 | [error.error( |
| 199 | "m.emb", external_type_ir.attribute[0].value.source_location, |
| 200 | "Attribute 'is_integer' must have a constant boolean value.")] |
| 201 | ], attribute_checker.normalize_and_verify(external_ir)) |
| 202 | |
| 203 | def test_rejects_addressable_unit_size_with_non_constant_value(self): |
| 204 | external_ir = _make_ir_from_emb( |
| 205 | "external Foo:\n" |
| 206 | " [is_integer: true]\n" |
| 207 | " [addressable_unit_size: $static_size_in_bits]\n") |
| 208 | external_type_ir = external_ir.module[0].type[0] |
| 209 | self.assertEqual([ |
| 210 | [error.error( |
| 211 | "m.emb", external_type_ir.attribute[1].value.source_location, |
| 212 | "Attribute 'addressable_unit_size' must have a constant value.")] |
| 213 | ], attribute_checker.normalize_and_verify(external_ir)) |
| 214 | |
| 215 | def test_rejects_external_with_wrong_addressable_unit_size_attribute(self): |
| 216 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 217 | " [addressable_unit_size: 4]\n") |
| 218 | external_type_ir = external_ir.module[0].type[0] |
| 219 | self.assertEqual([ |
| 220 | [error.error( |
| 221 | "m.emb", external_type_ir.source_location, |
| 222 | "Only values '1' (bit) and '8' (byte) are allowed for the " |
| 223 | "'addressable_unit_size' attribute")] |
| 224 | ], attribute_checker.normalize_and_verify(external_ir)) |
| 225 | |
| 226 | def test_rejects_duplicate_attribute(self): |
| 227 | ir = _make_ir_from_emb("external Foo:\n" |
| 228 | " [is_integer: true]\n" |
| 229 | " [is_integer: true]\n") |
| 230 | self.assertEqual([[ |
| 231 | error.error("m.emb", ir.module[0].type[0].attribute[1].source_location, |
| 232 | "Duplicate attribute 'is_integer'."), |
| 233 | error.note("m.emb", ir.module[0].type[0].attribute[0].source_location, |
| 234 | "Original attribute"), |
| 235 | ]], attribute_checker.normalize_and_verify(ir)) |
| 236 | |
| 237 | def test_rejects_duplicate_default_attribute(self): |
| 238 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 239 | '[$default byte_order: "LittleEndian"]\n') |
| 240 | self.assertEqual( |
| 241 | [[ |
| 242 | error.error("m.emb", ir.module[0].attribute[1].source_location, |
| 243 | "Duplicate attribute 'byte_order'."), |
| 244 | error.note("m.emb", ir.module[0].attribute[0].source_location, |
| 245 | "Original attribute"), |
| 246 | ]], attribute_checker.normalize_and_verify(ir)) |
| 247 | |
| 248 | def test_rejects_unknown_attribute(self): |
| 249 | ir = _make_ir_from_emb("[gibberish: true]\n") |
| 250 | attr = ir.module[0].attribute[0] |
| 251 | self.assertEqual([[ |
| 252 | error.error("m.emb", attr.name.source_location, |
| 253 | "Unknown attribute 'gibberish' on module 'm.emb'.") |
| 254 | ]], attribute_checker.normalize_and_verify(ir)) |
| 255 | |
| 256 | def test_rejects_non_constant_attribute(self): |
| 257 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 258 | "struct Foo:\n" |
| 259 | " [fixed_size_in_bits: field1]\n" |
| 260 | " 0 [+2] UInt field1\n") |
| 261 | attr = ir.module[0].type[0].attribute[0] |
| 262 | self.assertEqual( |
| 263 | [[ |
| 264 | error.error( |
| 265 | "m.emb", attr.value.source_location, |
| 266 | "Attribute 'fixed_size_in_bits' must have a constant value.") |
| 267 | ]], |
| 268 | attribute_checker.normalize_and_verify(ir)) |
| 269 | |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 270 | def test_rejects_attribute_missing_required_back_end_specifier(self): |
| 271 | ir = _make_ir_from_emb('[namespace: "abc"]\n') |
| 272 | attr = ir.module[0].attribute[0] |
| 273 | self.assertEqual([[ |
| 274 | error.error("m.emb", attr.name.source_location, |
| 275 | "Unknown attribute 'namespace' on module 'm.emb'.") |
| 276 | ]], attribute_checker.normalize_and_verify(ir)) |
| 277 | |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 278 | def test_accepts_attribute_with_default_known_back_end_specifier(self): |
| 279 | ir = _make_ir_from_emb('[(cpp) namespace: "abc"]\n') |
| 280 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 281 | |
| 282 | def test_rejects_attribute_with_specified_back_end_specifier(self): |
| 283 | ir = _make_ir_from_emb('[(c) namespace: "abc"]\n' |
| 284 | '[expected_back_ends: "c, cpp"]\n') |
| 285 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 286 | |
| 287 | def test_rejects_cpp_backend_attribute_when_not_in_expected_back_ends(self): |
| 288 | ir = _make_ir_from_emb('[(cpp) namespace: "abc"]\n' |
| 289 | '[expected_back_ends: "c"]\n') |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 290 | attr = ir.module[0].attribute[0] |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 291 | self.maxDiff = 200000 |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 292 | self.assertEqual([[ |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 293 | error.error( |
| 294 | "m.emb", attr.back_end.source_location, |
| 295 | "Back end specifier 'cpp' does not match any expected back end " |
| 296 | "specifier for this file: 'c'. Add or update the " |
| 297 | "'[expected_back_ends: \"c, cpp\"]' attribute at the file level if " |
| 298 | "this back end specifier is intentional.") |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 299 | ]], attribute_checker.normalize_and_verify(ir)) |
| 300 | |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 301 | def test_rejects_expected_back_ends_with_bad_back_end(self): |
| 302 | ir = _make_ir_from_emb('[expected_back_ends: "c++"]\n') |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 303 | attr = ir.module[0].attribute[0] |
| 304 | self.assertEqual([[ |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 305 | error.error( |
| 306 | "m.emb", attr.value.source_location, |
| 307 | "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| 308 | "back end specifiers (like \"cpp, proto\")), not \"c++\".") |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 309 | ]], attribute_checker.normalize_and_verify(ir)) |
| 310 | |
Dmitri Prime | 5b38126 | 2023-05-01 23:55:07 -0700 | [diff] [blame] | 311 | def test_rejects_expected_back_ends_with_no_comma(self): |
| 312 | ir = _make_ir_from_emb('[expected_back_ends: "cpp z"]\n') |
| 313 | attr = ir.module[0].attribute[0] |
| 314 | self.assertEqual([[ |
| 315 | error.error( |
| 316 | "m.emb", attr.value.source_location, |
| 317 | "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| 318 | "back end specifiers (like \"cpp, proto\")), not \"cpp z\".") |
| 319 | ]], attribute_checker.normalize_and_verify(ir)) |
| 320 | |
| 321 | def test_rejects_expected_back_ends_with_extra_commas(self): |
| 322 | ir = _make_ir_from_emb('[expected_back_ends: "cpp,,z"]\n') |
| 323 | attr = ir.module[0].attribute[0] |
| 324 | self.assertEqual([[ |
| 325 | error.error( |
| 326 | "m.emb", attr.value.source_location, |
| 327 | "Attribute 'expected_back_ends' must be a comma-delimited list of " |
| 328 | "back end specifiers (like \"cpp, proto\")), not \"cpp,,z\".") |
| 329 | ]], attribute_checker.normalize_and_verify(ir)) |
| 330 | |
| 331 | def test_accepts_empty_expected_back_ends(self): |
| 332 | ir = _make_ir_from_emb('[expected_back_ends: ""]\n') |
| 333 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 334 | |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 335 | def test_adds_byte_order_attributes_from_default(self): |
| 336 | ir = _make_ir_from_emb('[$default byte_order: "BigEndian"]\n' |
| 337 | "struct Foo:\n" |
| 338 | " 0 [+2] UInt bar\n" |
| 339 | " 2 [+2] UInt baz\n" |
| 340 | ' [byte_order: "LittleEndian"]\n') |
| 341 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 342 | byte_order_attr = ir_util.get_attribute( |
| 343 | ir.module[0].type[0].structure.field[0].attribute, _BYTE_ORDER) |
| 344 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 345 | self.assertEqual("BigEndian", byte_order_attr.string_constant.text) |
| 346 | byte_order_attr = ir_util.get_attribute( |
| 347 | ir.module[0].type[0].structure.field[1].attribute, _BYTE_ORDER) |
| 348 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 349 | self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| 350 | |
| 351 | def test_adds_null_byte_order_attributes(self): |
| 352 | ir = _make_ir_from_emb("struct Foo:\n" |
| 353 | " 0 [+1] UInt bar\n" |
| 354 | " 1 [+1] UInt baz\n" |
| 355 | ' [byte_order: "LittleEndian"]\n' |
| 356 | " 2 [+2] UInt:8[] baseball\n" |
| 357 | " 4 [+2] UInt:8[] bat\n" |
| 358 | ' [byte_order: "LittleEndian"]\n') |
| 359 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 360 | structure = ir.module[0].type[0].structure |
| 361 | byte_order_attr = ir_util.get_attribute( |
| 362 | structure.field[0].attribute, _BYTE_ORDER) |
| 363 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 364 | self.assertEqual("Null", byte_order_attr.string_constant.text) |
| 365 | self.assertEqual(structure.field[0].source_location, |
| 366 | byte_order_attr.source_location) |
| 367 | byte_order_attr = ir_util.get_attribute(structure.field[1].attribute, |
| 368 | _BYTE_ORDER) |
| 369 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 370 | self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| 371 | byte_order_attr = ir_util.get_attribute(structure.field[2].attribute, |
| 372 | _BYTE_ORDER) |
| 373 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 374 | self.assertEqual("Null", byte_order_attr.string_constant.text) |
| 375 | self.assertEqual(structure.field[2].source_location, |
| 376 | byte_order_attr.source_location) |
| 377 | byte_order_attr = ir_util.get_attribute(structure.field[3].attribute, |
| 378 | _BYTE_ORDER) |
| 379 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 380 | self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) |
| 381 | |
| 382 | def test_disallows_default_byte_order_on_field(self): |
| 383 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 384 | "struct Foo:\n" |
| 385 | " 0 [+2] UInt bar\n" |
| 386 | ' [$default byte_order: "LittleEndian"]\n') |
| 387 | default_byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 388 | self.assertEqual( |
| 389 | [[error.error( |
| 390 | "m.emb", default_byte_order.name.source_location, |
| 391 | "Attribute 'byte_order' may not be defaulted on struct field 'bar'." |
| 392 | )]], |
| 393 | attribute_checker.normalize_and_verify(ir)) |
| 394 | |
| 395 | def test_disallows_default_byte_order_on_bits(self): |
| 396 | ir = _make_ir_from_emb("bits Foo:\n" |
| 397 | ' [$default byte_order: "LittleEndian"]\n' |
| 398 | " 0 [+2] UInt bar\n") |
| 399 | default_byte_order = ir.module[0].type[0].attribute[0] |
| 400 | self.assertEqual( |
| 401 | [[error.error( |
| 402 | "m.emb", default_byte_order.name.source_location, |
| 403 | "Attribute 'byte_order' may not be defaulted on bits 'Foo'.")]], |
| 404 | attribute_checker.normalize_and_verify(ir)) |
| 405 | |
| 406 | def test_disallows_default_byte_order_on_enum(self): |
| 407 | ir = _make_ir_from_emb("enum Foo:\n" |
| 408 | ' [$default byte_order: "LittleEndian"]\n' |
| 409 | " BAR = 1\n") |
| 410 | default_byte_order = ir.module[0].type[0].attribute[0] |
| 411 | self.assertEqual( |
| 412 | [[error.error( |
| 413 | "m.emb", default_byte_order.name.source_location, |
| 414 | "Attribute 'byte_order' may not be defaulted on enum 'Foo'.")]], |
| 415 | attribute_checker.normalize_and_verify(ir)) |
| 416 | |
| 417 | def test_adds_byte_order_from_scoped_default(self): |
| 418 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 419 | "struct Foo:\n" |
| 420 | ' [$default byte_order: "BigEndian"]\n' |
| 421 | " 0 [+2] UInt bar\n") |
| 422 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 423 | byte_order_attr = ir_util.get_attribute( |
| 424 | ir.module[0].type[0].structure.field[0].attribute, _BYTE_ORDER) |
| 425 | self.assertTrue(byte_order_attr.HasField("string_constant")) |
| 426 | self.assertEqual("BigEndian", byte_order_attr.string_constant.text) |
| 427 | |
| 428 | def test_disallows_unknown_byte_order(self): |
| 429 | ir = _make_ir_from_emb("struct Foo:\n" |
| 430 | " 0 [+2] UInt bar\n" |
| 431 | ' [byte_order: "NoEndian"]\n') |
| 432 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 433 | self.assertEqual( |
| 434 | [[error.error( |
| 435 | "m.emb", byte_order.value.source_location, |
| 436 | "Attribute 'byte_order' must be 'BigEndian' or 'LittleEndian' or " |
| 437 | "'Null'.")]], |
| 438 | attribute_checker.normalize_and_verify(ir)) |
| 439 | |
| 440 | def test_disallows_unknown_default_byte_order(self): |
| 441 | ir = _make_ir_from_emb('[$default byte_order: "NoEndian"]\n') |
| 442 | default_byte_order = ir.module[0].attribute[0] |
| 443 | self.assertEqual( |
| 444 | [[error.error( |
| 445 | "m.emb", default_byte_order.value.source_location, |
| 446 | "Attribute 'byte_order' must be 'BigEndian' or 'LittleEndian' or " |
| 447 | "'Null'.")]], |
| 448 | attribute_checker.normalize_and_verify(ir)) |
| 449 | |
| 450 | def test_disallows_byte_order_on_non_byte_order_dependent_fields(self): |
| 451 | ir = _make_ir_from_emb("struct Foo:\n" |
| 452 | ' [$default byte_order: "LittleEndian"]\n' |
| 453 | " 0 [+2] UInt uint\n" |
| 454 | "struct Bar:\n" |
| 455 | " 0 [+2] Foo foo\n" |
| 456 | ' [byte_order: "LittleEndian"]\n') |
| 457 | byte_order = ir.module[0].type[1].structure.field[0].attribute[0] |
| 458 | self.assertEqual( |
| 459 | [[error.error( |
| 460 | "m.emb", byte_order.value.source_location, |
| 461 | "Attribute 'byte_order' not allowed on field which is not byte " |
| 462 | "order dependent.")]], |
| 463 | attribute_checker.normalize_and_verify(ir)) |
| 464 | |
| 465 | def test_disallows_byte_order_on_virtual_field(self): |
| 466 | ir = _make_ir_from_emb("struct Foo:\n" |
| 467 | " let x = 10\n" |
| 468 | ' [byte_order: "LittleEndian"]\n') |
| 469 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 470 | self.assertEqual( |
| 471 | [[error.error( |
| 472 | "m.emb", byte_order.name.source_location, |
| 473 | "Unknown attribute 'byte_order' on virtual struct field 'x'.")]], |
| 474 | attribute_checker.normalize_and_verify(ir)) |
| 475 | |
| 476 | def test_disallows_null_byte_order_on_multibyte_fields(self): |
| 477 | ir = _make_ir_from_emb("struct Foo:\n" |
| 478 | " 0 [+2] UInt uint\n" |
| 479 | ' [byte_order: "Null"]\n') |
| 480 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 481 | self.assertEqual( |
| 482 | [[error.error( |
| 483 | "m.emb", byte_order.value.source_location, |
| 484 | "Attribute 'byte_order' may only be 'Null' for one-byte fields.")]], |
| 485 | attribute_checker.normalize_and_verify(ir)) |
| 486 | |
| 487 | def test_disallows_null_byte_order_on_multibyte_array_elements(self): |
| 488 | ir = _make_ir_from_emb("struct Foo:\n" |
| 489 | " 0 [+4] UInt:16[] uint\n" |
| 490 | ' [byte_order: "Null"]\n') |
| 491 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 492 | self.assertEqual( |
| 493 | [[error.error( |
| 494 | "m.emb", byte_order.value.source_location, |
| 495 | "Attribute 'byte_order' may only be 'Null' for one-byte fields.")]], |
| 496 | attribute_checker.normalize_and_verify(ir)) |
| 497 | |
| 498 | def test_requires_byte_order_on_byte_order_dependent_fields(self): |
| 499 | ir = _make_ir_from_emb("struct Foo:\n" |
| 500 | " 0 [+2] UInt uint\n") |
| 501 | field = ir.module[0].type[0].structure.field[0] |
| 502 | self.assertEqual( |
| 503 | [[error.error( |
| 504 | "m.emb", field.source_location, |
| 505 | "Attribute 'byte_order' required on field which is byte order " |
| 506 | "dependent.")]], |
| 507 | attribute_checker.normalize_and_verify(ir)) |
| 508 | |
| 509 | def test_disallows_unknown_text_output_attribute(self): |
| 510 | ir = _make_ir_from_emb("struct Foo:\n" |
| 511 | " 0 [+2] UInt bar\n" |
| 512 | ' [text_output: "None"]\n') |
| 513 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 514 | self.assertEqual( |
| 515 | [[error.error( |
| 516 | "m.emb", byte_order.value.source_location, |
| 517 | "Attribute 'text_output' must be 'Emit' or 'Skip'.")]], |
| 518 | attribute_checker.normalize_and_verify(ir)) |
| 519 | |
| 520 | def test_disallows_non_string_text_output_attribute(self): |
| 521 | ir = _make_ir_from_emb("struct Foo:\n" |
| 522 | " 0 [+2] UInt bar\n" |
| 523 | " [text_output: 0]\n") |
| 524 | byte_order = ir.module[0].type[0].structure.field[0].attribute[0] |
| 525 | self.assertEqual( |
| 526 | [[error.error( |
| 527 | "m.emb", byte_order.value.source_location, |
| 528 | "Attribute 'text_output' must be 'Emit' or 'Skip'.")]], |
| 529 | attribute_checker.normalize_and_verify(ir)) |
| 530 | |
| 531 | def test_allows_skip_text_output_attribute_on_physical_field(self): |
| 532 | ir = _make_ir_from_emb("struct Foo:\n" |
| 533 | " 0 [+1] UInt bar\n" |
| 534 | ' [text_output: "Skip"]\n') |
| 535 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 536 | |
| 537 | def test_allows_skip_text_output_attribute_on_virtual_field(self): |
| 538 | ir = _make_ir_from_emb("struct Foo:\n" |
| 539 | " let x = 10\n" |
| 540 | ' [text_output: "Skip"]\n') |
| 541 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 542 | |
| 543 | def test_allows_emit_text_output_attribute_on_physical_field(self): |
| 544 | ir = _make_ir_from_emb("struct Foo:\n" |
| 545 | " 0 [+1] UInt bar\n" |
| 546 | ' [text_output: "Emit"]\n') |
| 547 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 548 | |
| 549 | def test_adds_bit_addressable_unit_to_external(self): |
| 550 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 551 | " [addressable_unit_size: 1]\n") |
| 552 | self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
Eric Rahm | 12c5e84 | 2024-03-22 09:37:24 -0700 | [diff] [blame] | 553 | self.assertEqual(ir_data.AddressableUnit.BIT, |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 554 | external_ir.module[0].type[0].addressable_unit) |
| 555 | |
| 556 | def test_adds_byte_addressable_unit_to_external(self): |
| 557 | external_ir = _make_ir_from_emb("external Foo:\n" |
| 558 | " [addressable_unit_size: 8]\n") |
| 559 | self.assertEqual([], attribute_checker.normalize_and_verify(external_ir)) |
Eric Rahm | 12c5e84 | 2024-03-22 09:37:24 -0700 | [diff] [blame] | 560 | self.assertEqual(ir_data.AddressableUnit.BYTE, |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 561 | external_ir.module[0].type[0].addressable_unit) |
| 562 | |
| 563 | def test_rejects_requires_using_array(self): |
| 564 | ir = _make_ir_from_emb("struct Foo:\n" |
| 565 | " 0 [+4] UInt:8[] array\n" |
| 566 | " [requires: this]\n") |
| 567 | field_ir = ir.module[0].type[0].structure.field[0] |
| 568 | self.assertEqual( |
| 569 | [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| 570 | "Attribute 'requires' must have a boolean value.")]], |
| 571 | attribute_checker.normalize_and_verify(ir)) |
| 572 | |
| 573 | def test_rejects_requires_on_array(self): |
| 574 | ir = _make_ir_from_emb("struct Foo:\n" |
| 575 | " 0 [+4] UInt:8[] array\n" |
| 576 | " [requires: false]\n") |
| 577 | field_ir = ir.module[0].type[0].structure.field[0] |
| 578 | self.assertEqual( |
| 579 | [[ |
| 580 | error.error("m.emb", field_ir.attribute[0].value.source_location, |
| 581 | "Attribute 'requires' is only allowed on integer, " |
| 582 | "enumeration, or boolean fields, not arrays."), |
| 583 | error.note("m.emb", field_ir.type.source_location, |
| 584 | "Field type."), |
| 585 | ]], |
| 586 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 587 | |
| 588 | def test_rejects_requires_on_struct(self): |
| 589 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 590 | "struct Foo:\n" |
| 591 | " 0 [+4] Bar bar\n" |
| 592 | " [requires: false]\n" |
| 593 | "struct Bar:\n" |
| 594 | " 0 [+4] UInt uint\n") |
| 595 | field_ir = ir.module[0].type[0].structure.field[0] |
| 596 | self.assertEqual( |
| 597 | [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| 598 | "Attribute 'requires' is only allowed on integer, " |
| 599 | "enumeration, or boolean fields.")]], |
| 600 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 601 | |
| 602 | def test_rejects_requires_on_float(self): |
| 603 | ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' |
| 604 | "struct Foo:\n" |
| 605 | " 0 [+4] Float float\n" |
| 606 | " [requires: false]\n") |
| 607 | field_ir = ir.module[0].type[0].structure.field[0] |
| 608 | self.assertEqual( |
| 609 | [[error.error("m.emb", field_ir.attribute[0].value.source_location, |
| 610 | "Attribute 'requires' is only allowed on integer, " |
| 611 | "enumeration, or boolean fields.")]], |
| 612 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 613 | |
reventlov | c50913d | 2022-04-18 15:28:36 -0700 | [diff] [blame] | 614 | def test_adds_false_is_signed_attribute(self): |
| 615 | ir = _make_ir_from_emb("enum Foo:\n" |
| 616 | " ZERO = 0\n") |
| 617 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 618 | enum = ir.module[0].type[0] |
| 619 | is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| 620 | self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| 621 | self.assertFalse(is_signed_attr.expression.boolean_constant.value) |
| 622 | |
| 623 | def test_leaves_is_signed_attribute(self): |
| 624 | ir = _make_ir_from_emb("enum Foo:\n" |
| 625 | " [is_signed: true]\n" |
| 626 | " ZERO = 0\n") |
| 627 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 628 | enum = ir.module[0].type[0] |
| 629 | is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| 630 | self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| 631 | self.assertTrue(is_signed_attr.expression.boolean_constant.value) |
| 632 | |
| 633 | def test_adds_true_is_signed_attribute(self): |
| 634 | ir = _make_ir_from_emb("enum Foo:\n" |
| 635 | " NEGATIVE_ONE = -1\n") |
| 636 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 637 | enum = ir.module[0].type[0] |
| 638 | is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) |
| 639 | self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) |
| 640 | self.assertTrue(is_signed_attr.expression.boolean_constant.value) |
| 641 | |
| 642 | def test_adds_max_bits_attribute(self): |
| 643 | ir = _make_ir_from_emb("enum Foo:\n" |
| 644 | " ZERO = 0\n") |
| 645 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 646 | enum = ir.module[0].type[0] |
| 647 | max_bits_attr = ir_util.get_attribute(enum.attribute, _MAX_BITS) |
| 648 | self.assertTrue(max_bits_attr.expression.HasField("constant")) |
| 649 | self.assertEqual("64", max_bits_attr.expression.constant.value) |
| 650 | |
| 651 | def test_leaves_max_bits_attribute(self): |
| 652 | ir = _make_ir_from_emb("enum Foo:\n" |
| 653 | " [maximum_bits: 32]\n" |
| 654 | " ZERO = 0\n") |
| 655 | self.assertEqual([], attribute_checker.normalize_and_verify(ir)) |
| 656 | enum = ir.module[0].type[0] |
| 657 | max_bits_attr = ir_util.get_attribute(enum.attribute, _MAX_BITS) |
| 658 | self.assertTrue(max_bits_attr.expression.HasField("constant")) |
| 659 | self.assertEqual("32", max_bits_attr.expression.constant.value) |
| 660 | |
| 661 | def test_rejects_too_small_max_bits(self): |
| 662 | ir = _make_ir_from_emb("enum Foo:\n" |
| 663 | " [maximum_bits: 0]\n" |
| 664 | " ZERO = 0\n") |
| 665 | attribute_ir = ir.module[0].type[0].attribute[0] |
| 666 | self.assertEqual( |
| 667 | [[error.error( |
| 668 | "m.emb", attribute_ir.value.source_location, |
| 669 | "'maximum_bits' on an 'enum' must be between 1 and 64.")]], |
| 670 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 671 | |
| 672 | def test_rejects_too_large_max_bits(self): |
| 673 | ir = _make_ir_from_emb("enum Foo:\n" |
| 674 | " [maximum_bits: 65]\n" |
| 675 | " ZERO = 0\n") |
| 676 | attribute_ir = ir.module[0].type[0].attribute[0] |
| 677 | self.assertEqual( |
| 678 | [[error.error( |
| 679 | "m.emb", attribute_ir.value.source_location, |
| 680 | "'maximum_bits' on an 'enum' must be between 1 and 64.")]], |
| 681 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 682 | |
Jason Graffius | 1119723 | 2023-05-17 19:12:14 -0400 | [diff] [blame] | 683 | def test_rejects_unknown_enum_value_attribute(self): |
| 684 | ir = _make_ir_from_emb("enum Foo:\n" |
| 685 | " BAR = 0 \n" |
| 686 | " [bad_attr: true]\n") |
| 687 | attribute_ir = ir.module[0].type[0].enumeration.value[0].attribute[0] |
| 688 | self.assertNotEqual([], attribute_checker.normalize_and_verify(ir)) |
| 689 | self.assertEqual( |
| 690 | [[error.error( |
| 691 | "m.emb", attribute_ir.name.source_location, |
| 692 | "Unknown attribute 'bad_attr' on enum value 'BAR'.")]], |
| 693 | error.filter_errors(attribute_checker.normalize_and_verify(ir))) |
| 694 | |
Ben Olmstead | c0d7784 | 2019-07-31 17:34:05 -0700 | [diff] [blame] | 695 | |
| 696 | if __name__ == "__main__": |
| 697 | unittest.main() |