1# Copyright 2024 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 util.ir_data_utils.""" 16 17import dataclasses 18import enum 19import sys 20from typing import Optional 21import unittest 22from compiler.util import expression_parser 23from compiler.util import ir_data 24from compiler.util import ir_data_fields 25from compiler.util import ir_data_utils 26 27 28class TestEnum(enum.Enum): 29 """Used to test python Enum handling.""" 30 31 UNKNOWN = 0 32 VALUE_1 = 1 33 VALUE_2 = 2 34 35 36@dataclasses.dataclass 37class Opaque(ir_data.Message): 38 """Used for testing data field helpers""" 39 40 41@dataclasses.dataclass 42class ClassWithUnion(ir_data.Message): 43 """Used for testing data field helpers""" 44 45 opaque: Optional[Opaque] = ir_data_fields.oneof_field("type") 46 integer: Optional[int] = ir_data_fields.oneof_field("type") 47 boolean: Optional[bool] = ir_data_fields.oneof_field("type") 48 enumeration: Optional[TestEnum] = ir_data_fields.oneof_field("type") 49 non_union_field: int = 0 50 51 52@dataclasses.dataclass 53class ClassWithTwoUnions(ir_data.Message): 54 """Used for testing data field helpers""" 55 56 opaque: Optional[Opaque] = ir_data_fields.oneof_field("type_1") 57 integer: Optional[int] = ir_data_fields.oneof_field("type_1") 58 boolean: Optional[bool] = ir_data_fields.oneof_field("type_2") 59 enumeration: Optional[TestEnum] = ir_data_fields.oneof_field("type_2") 60 non_union_field: int = 0 61 seq_field: list[int] = ir_data_fields.list_field(int) 62 63 64class IrDataUtilsTest(unittest.TestCase): 65 """Tests for the miscellaneous utility functions in ir_data_utils.py.""" 66 67 def test_field_specs(self): 68 """Tests the `field_specs` method""" 69 fields = ir_data_utils.field_specs(ir_data.TypeDefinition) 70 self.assertIsNotNone(fields) 71 expected_fields = ( 72 "external", 73 "enumeration", 74 "structure", 75 "name", 76 "attribute", 77 "documentation", 78 "subtype", 79 "addressable_unit", 80 "runtime_parameter", 81 "source_location", 82 ) 83 self.assertEqual(len(fields), len(expected_fields)) 84 field_names = fields.keys() 85 for k in expected_fields: 86 self.assertIn(k, field_names) 87 88 # Try a sequence 89 expected_field = ir_data_fields.make_field_spec( 90 "attribute", ir_data.Attribute, ir_data_fields.FieldContainer.LIST, None 91 ) 92 self.assertEqual(fields["attribute"], expected_field) 93 94 # Try a scalar 95 expected_field = ir_data_fields.make_field_spec( 96 "addressable_unit", 97 ir_data.AddressableUnit, 98 ir_data_fields.FieldContainer.OPTIONAL, 99 None, 100 ) 101 self.assertEqual(fields["addressable_unit"], expected_field) 102 103 # Try a IR data class 104 expected_field = ir_data_fields.make_field_spec( 105 "source_location", 106 ir_data.Location, 107 ir_data_fields.FieldContainer.OPTIONAL, 108 None, 109 ) 110 self.assertEqual(fields["source_location"], expected_field) 111 112 # Try an oneof field 113 expected_field = ir_data_fields.make_field_spec( 114 "external", 115 ir_data.External, 116 ir_data_fields.FieldContainer.OPTIONAL, 117 oneof="type", 118 ) 119 self.assertEqual(fields["external"], expected_field) 120 121 # Try non-optional scalar 122 fields = ir_data_utils.field_specs(ir_data.Position) 123 expected_field = ir_data_fields.make_field_spec( 124 "line", int, ir_data_fields.FieldContainer.NONE, None 125 ) 126 self.assertEqual(fields["line"], expected_field) 127 128 fields = ir_data_utils.field_specs(ir_data.ArrayType) 129 expected_field = ir_data_fields.make_field_spec( 130 "base_type", ir_data.Type, ir_data_fields.FieldContainer.OPTIONAL, None 131 ) 132 self.assertEqual(fields["base_type"], expected_field) 133 134 def test_is_sequence(self): 135 """Tests for the `FieldSpec.is_sequence` helper""" 136 type_def = ir_data.TypeDefinition( 137 attribute=[ 138 ir_data.Attribute( 139 value=ir_data.AttributeValue(expression=ir_data.Expression()), 140 name=ir_data.Word(text="phil"), 141 ), 142 ] 143 ) 144 fields = ir_data_utils.field_specs(ir_data.TypeDefinition) 145 # Test against a repeated field 146 self.assertTrue(fields["attribute"].is_sequence) 147 # Test against a nested IR data type 148 self.assertFalse(fields["name"].is_sequence) 149 # Test against a plain scalar type 150 fields = ir_data_utils.field_specs(type_def.attribute[0]) 151 self.assertFalse(fields["is_default"].is_sequence) 152 153 def test_is_dataclass(self): 154 """Tests FieldSpec.is_dataclass against ir_data""" 155 type_def = ir_data.TypeDefinition( 156 attribute=[ 157 ir_data.Attribute( 158 value=ir_data.AttributeValue(expression=ir_data.Expression()), 159 name=ir_data.Word(text="phil"), 160 ), 161 ] 162 ) 163 fields = ir_data_utils.field_specs(ir_data.TypeDefinition) 164 # Test against a repeated field that holds IR data structs 165 self.assertTrue(fields["attribute"].is_dataclass) 166 # Test against a nested IR data type 167 self.assertTrue(fields["name"].is_dataclass) 168 # Test against a plain scalar type 169 fields = ir_data_utils.field_specs(type_def.attribute[0]) 170 self.assertFalse(fields["is_default"].is_dataclass) 171 # Test against a repeated field that holds scalars 172 fields = ir_data_utils.field_specs(ir_data.Structure) 173 self.assertFalse(fields["fields_in_dependency_order"].is_dataclass) 174 175 def test_get_set_fields(self): 176 """Tests that get set fields works""" 177 type_def = ir_data.TypeDefinition( 178 attribute=[ 179 ir_data.Attribute( 180 value=ir_data.AttributeValue(expression=ir_data.Expression()), 181 name=ir_data.Word(text="phil"), 182 ), 183 ] 184 ) 185 set_fields = ir_data_utils.get_set_fields(type_def) 186 expected_fields = set( 187 ["attribute", "documentation", "subtype", "runtime_parameter"] 188 ) 189 self.assertEqual(len(set_fields), len(expected_fields)) 190 found_fields = set() 191 for k, v in set_fields: 192 self.assertIn(k.name, expected_fields) 193 found_fields.add(k.name) 194 self.assertEqual(v, getattr(type_def, k.name)) 195 196 self.assertSetEqual(found_fields, expected_fields) 197 198 def test_copy(self): 199 """Tests the `copy` helper""" 200 attribute = ir_data.Attribute( 201 value=ir_data.AttributeValue(expression=ir_data.Expression()), 202 name=ir_data.Word(text="phil"), 203 ) 204 attribute_copy = ir_data_utils.copy(attribute) 205 206 # Should be equivalent 207 self.assertEqual(attribute, attribute_copy) 208 # But not the same instance 209 self.assertIsNot(attribute, attribute_copy) 210 211 # Let's do a sequence 212 type_def = ir_data.TypeDefinition(attribute=[attribute]) 213 type_def_copy = ir_data_utils.copy(type_def) 214 215 # Should be equivalent 216 self.assertEqual(type_def, type_def_copy) 217 # But not the same instance 218 self.assertIsNot(type_def, type_def_copy) 219 self.assertIsNot(type_def.attribute, type_def_copy.attribute) 220 221 def test_update(self): 222 """Tests the `update` helper""" 223 attribute_template = ir_data.Attribute( 224 value=ir_data.AttributeValue(expression=ir_data.Expression()), 225 name=ir_data.Word(text="phil"), 226 ) 227 attribute = ir_data.Attribute(is_default=True) 228 ir_data_utils.update(attribute, attribute_template) 229 self.assertIsNotNone(attribute.value) 230 self.assertIsNot(attribute.value, attribute_template.value) 231 self.assertIsNotNone(attribute.name) 232 self.assertIsNot(attribute.name, attribute_template.name) 233 234 # Value not present in template should be untouched 235 self.assertTrue(attribute.is_default) 236 237 238class IrDataBuilderTest(unittest.TestCase): 239 """Tests for IrDataBuilder""" 240 241 def test_ir_data_builder(self): 242 """Tests that basic builder chains work""" 243 # We start with an empty type 244 type_def = ir_data.TypeDefinition() 245 self.assertFalse(type_def.HasField("name")) 246 self.assertIsNone(type_def.name) 247 248 # Now setup a builder 249 builder = ir_data_utils.builder(type_def) 250 251 # Assign to a sub-child 252 builder.name.name = ir_data.Word(text="phil") 253 254 # Verify the wrapped struct is updated 255 self.assertIsNotNone(type_def.name) 256 self.assertIsNotNone(type_def.name.name) 257 self.assertIsNotNone(type_def.name.name.text) 258 self.assertEqual(type_def.name.name.text, "phil") 259 260 def test_ir_data_builder_bad_field(self): 261 """Tests accessing an undefined field name fails""" 262 type_def = ir_data.TypeDefinition() 263 builder = ir_data_utils.builder(type_def) 264 self.assertRaises(AttributeError, lambda: builder.foo) 265 # Make sure it's not set on our IR data class either 266 self.assertRaises(AttributeError, getattr, type_def, "foo") 267 268 def test_ir_data_builder_sequence(self): 269 """Tests that sequences are properly wrapped""" 270 # We start with an empty type 271 type_def = ir_data.TypeDefinition() 272 self.assertTrue(type_def.HasField("attribute")) 273 self.assertEqual(len(type_def.attribute), 0) 274 275 # Now setup a builder 276 builder = ir_data_utils.builder(type_def) 277 278 # Assign to a sequence 279 attribute = ir_data.Attribute( 280 value=ir_data.AttributeValue(expression=ir_data.Expression()), 281 name=ir_data.Word(text="phil"), 282 ) 283 284 builder.attribute.append(attribute) 285 self.assertEqual(builder.attribute, [attribute]) 286 self.assertTrue(type_def.HasField("attribute")) 287 self.assertEqual(len(type_def.attribute), 1) 288 self.assertEqual(type_def.attribute[0], attribute) 289 290 # Lets make it longer and then try iterating 291 builder.attribute.append(attribute) 292 self.assertEqual(len(type_def.attribute), 2) 293 for attr in builder.attribute: 294 # Modify the attributes 295 attr.name.text = "bob" 296 297 # Make sure we can build up auto-default entries from a sequence item 298 builder.attribute.append(ir_data.Attribute()) 299 builder.attribute[-1].value.expression = ir_data.Expression() 300 builder.attribute[-1].name.text = "bob" 301 302 # Create an attribute to compare against 303 new_attribute = ir_data.Attribute( 304 value=ir_data.AttributeValue(expression=ir_data.Expression()), 305 name=ir_data.Word(text="bob"), 306 ) 307 308 self.assertEqual(len(type_def.attribute), 3) 309 for attr in type_def.attribute: 310 self.assertEqual(attr, new_attribute) 311 312 # Make sure the list type is a CopyValuesList 313 self.assertIsInstance( 314 type_def.attribute, 315 ir_data_fields.CopyValuesList, 316 f"Instance is: {type(type_def.attribute)}", 317 ) 318 319 def test_copy_from(self) -> None: 320 """Tests that `CopyFrom` works.""" 321 location = ir_data.Location( 322 start=ir_data.Position(line=1, column=1), 323 end=ir_data.Position(line=1, column=2), 324 ) 325 expression_ir = ir_data.Expression(source_location=location) 326 template: ir_data.Expression = expression_parser.parse("x + y") 327 expression = ir_data_utils.builder(expression_ir) 328 expression.CopyFrom(template) 329 self.assertIsNotNone(expression_ir.function) 330 self.assertIsInstance(expression.function, ir_data_utils._IrDataBuilder) 331 self.assertIsInstance( 332 expression.function.args, ir_data_utils._IrDataSequenceBuilder 333 ) 334 self.assertTrue(expression_ir.function.args) 335 336 def test_copy_from_list(self): 337 specs = ir_data_utils.field_specs(ir_data.Function) 338 args_spec = specs["args"] 339 self.assertTrue(args_spec.is_dataclass) 340 template: ir_data.Expression = expression_parser.parse("x + y") 341 self.assertIsNotNone(template) 342 self.assertIsInstance(template, ir_data.Expression) 343 self.assertIsInstance(template.function, ir_data.Function) 344 self.assertIsInstance(template.function.args, ir_data_fields.CopyValuesList) 345 346 location = ir_data.Location( 347 start=ir_data.Position(line=1, column=1), 348 end=ir_data.Position(line=1, column=2), 349 ) 350 expression_ir = ir_data.Expression(source_location=location) 351 self.assertIsInstance(expression_ir, ir_data.Expression) 352 self.assertIsNone(expression_ir.function) 353 354 expression_builder = ir_data_utils.builder(expression_ir) 355 self.assertIsInstance(expression_builder, ir_data_utils._IrDataBuilder) 356 expression_builder.CopyFrom(template) 357 self.assertIsNotNone(expression_ir.function) 358 self.assertIsInstance(expression_ir.function, ir_data.Function) 359 self.assertIsNotNone(expression_ir.function.args) 360 self.assertIsInstance( 361 expression_ir.function.args, ir_data_fields.CopyValuesList 362 ) 363 364 self.assertIsInstance(expression_builder, ir_data_utils._IrDataBuilder) 365 self.assertIsInstance( 366 expression_builder.function, ir_data_utils._IrDataBuilder 367 ) 368 self.assertIsInstance( 369 expression_builder.function.args, ir_data_utils._IrDataSequenceBuilder 370 ) 371 372 def test_ir_data_builder_sequence_scalar(self): 373 """Tests that sequences of scalars function properly""" 374 # We start with an empty type 375 structure = ir_data.Structure() 376 377 # Now setup a builder 378 builder = ir_data_utils.builder(structure) 379 380 # Assign to a scalar sequence 381 builder.fields_in_dependency_order.append(12) 382 builder.fields_in_dependency_order.append(11) 383 384 self.assertTrue(structure.HasField("fields_in_dependency_order")) 385 self.assertEqual(len(structure.fields_in_dependency_order), 2) 386 self.assertEqual(structure.fields_in_dependency_order[0], 12) 387 self.assertEqual(structure.fields_in_dependency_order[1], 11) 388 self.assertEqual(builder.fields_in_dependency_order, [12, 11]) 389 390 new_structure = ir_data.Structure(fields_in_dependency_order=[12, 11]) 391 self.assertEqual(structure, new_structure) 392 393 def test_ir_data_builder_oneof(self): 394 value = ir_data.AttributeValue( 395 expression=ir_data.Expression( 396 boolean_constant=ir_data.BooleanConstant() 397 ) 398 ) 399 builder = ir_data_utils.builder(value) 400 self.assertTrue(builder.HasField("expression")) 401 self.assertFalse(builder.expression.boolean_constant.value) 402 builder.expression.boolean_constant.value = True 403 self.assertTrue(builder.expression.boolean_constant.value) 404 self.assertTrue(value.expression.boolean_constant.value) 405 406 bool_constant = value.expression.boolean_constant 407 self.assertIsInstance(bool_constant, ir_data.BooleanConstant) 408 409 410class IrDataSerializerTest(unittest.TestCase): 411 """Tests for IrDataSerializer""" 412 413 def test_ir_data_serializer_to_dict(self): 414 """Tests serialization with `IrDataSerializer.to_dict` with default settings""" 415 attribute = ir_data.Attribute( 416 value=ir_data.AttributeValue(expression=ir_data.Expression()), 417 name=ir_data.Word(text="phil"), 418 ) 419 420 serializer = ir_data_utils.IrDataSerializer(attribute) 421 raw_dict = serializer.to_dict() 422 expected = { 423 "name": {"text": "phil", "source_location": None}, 424 "value": { 425 "expression": { 426 "constant": None, 427 "constant_reference": None, 428 "function": None, 429 "field_reference": None, 430 "boolean_constant": None, 431 "builtin_reference": None, 432 "type": None, 433 "source_location": None, 434 }, 435 "string_constant": None, 436 "source_location": None, 437 }, 438 "back_end": None, 439 "is_default": None, 440 "source_location": None, 441 } 442 self.assertDictEqual(raw_dict, expected) 443 444 def test_ir_data_serializer_to_dict_exclude_none(self): 445 """Tests serialization with `IrDataSerializer.to_dict` when excluding None values""" 446 attribute = ir_data.Attribute( 447 value=ir_data.AttributeValue(expression=ir_data.Expression()), 448 name=ir_data.Word(text="phil"), 449 ) 450 serializer = ir_data_utils.IrDataSerializer(attribute) 451 raw_dict = serializer.to_dict(exclude_none=True) 452 expected = {"name": {"text": "phil"}, "value": {"expression": {}}} 453 self.assertDictEqual(raw_dict, expected) 454 455 def test_ir_data_serializer_to_dict_enum(self): 456 """Tests that serialization of `enum.Enum` values works properly""" 457 type_def = ir_data.TypeDefinition( 458 addressable_unit=ir_data.AddressableUnit.BYTE 459 ) 460 serializer = ir_data_utils.IrDataSerializer(type_def) 461 raw_dict = serializer.to_dict(exclude_none=True) 462 expected = {"addressable_unit": ir_data.AddressableUnit.BYTE} 463 self.assertDictEqual(raw_dict, expected) 464 465 def test_ir_data_serializer_from_dict(self): 466 """Tests deserializing IR data from a serialized dict""" 467 attribute = ir_data.Attribute( 468 value=ir_data.AttributeValue(expression=ir_data.Expression()), 469 name=ir_data.Word(text="phil"), 470 ) 471 serializer = ir_data_utils.IrDataSerializer(attribute) 472 raw_dict = serializer.to_dict(exclude_none=False) 473 new_attribute = serializer.from_dict(ir_data.Attribute, raw_dict) 474 self.assertEqual(attribute, new_attribute) 475 476 def test_ir_data_serializer_from_dict_enum(self): 477 """Tests that deserializing `enum.Enum` values works properly""" 478 type_def = ir_data.TypeDefinition( 479 addressable_unit=ir_data.AddressableUnit.BYTE 480 ) 481 482 serializer = ir_data_utils.IrDataSerializer(type_def) 483 raw_dict = serializer.to_dict(exclude_none=False) 484 new_type_def = serializer.from_dict(ir_data.TypeDefinition, raw_dict) 485 self.assertEqual(type_def, new_type_def) 486 487 def test_ir_data_serializer_from_dict_enum_is_str(self): 488 """Tests that deserializing `enum.Enum` values works properly when string constant is used""" 489 type_def = ir_data.TypeDefinition( 490 addressable_unit=ir_data.AddressableUnit.BYTE 491 ) 492 raw_dict = {"addressable_unit": "BYTE"} 493 serializer = ir_data_utils.IrDataSerializer(type_def) 494 new_type_def = serializer.from_dict(ir_data.TypeDefinition, raw_dict) 495 self.assertEqual(type_def, new_type_def) 496 497 def test_ir_data_serializer_from_dict_exclude_none(self): 498 """Tests that deserializing from a dict that excluded None values works properly""" 499 attribute = ir_data.Attribute( 500 value=ir_data.AttributeValue(expression=ir_data.Expression()), 501 name=ir_data.Word(text="phil"), 502 ) 503 504 serializer = ir_data_utils.IrDataSerializer(attribute) 505 raw_dict = serializer.to_dict(exclude_none=True) 506 new_attribute = ir_data_utils.IrDataSerializer.from_dict( 507 ir_data.Attribute, raw_dict 508 ) 509 self.assertEqual(attribute, new_attribute) 510 511 def test_from_dict_list(self): 512 function_args = [ 513 { 514 "constant": { 515 "value": "0", 516 "source_location": { 517 "start": {"line": 421, "column": 3}, 518 "end": {"line": 421, "column": 4}, 519 "is_synthetic": False, 520 }, 521 }, 522 "type": { 523 "integer": { 524 "modulus": "infinity", 525 "modular_value": "0", 526 "minimum_value": "0", 527 "maximum_value": "0", 528 } 529 }, 530 "source_location": { 531 "start": {"line": 421, "column": 3}, 532 "end": {"line": 421, "column": 4}, 533 "is_synthetic": False, 534 }, 535 }, 536 { 537 "constant": { 538 "value": "1", 539 "source_location": { 540 "start": {"line": 421, "column": 11}, 541 "end": {"line": 421, "column": 12}, 542 "is_synthetic": False, 543 }, 544 }, 545 "type": { 546 "integer": { 547 "modulus": "infinity", 548 "modular_value": "1", 549 "minimum_value": "1", 550 "maximum_value": "1", 551 } 552 }, 553 "source_location": { 554 "start": {"line": 421, "column": 11}, 555 "end": {"line": 421, "column": 12}, 556 "is_synthetic": False, 557 }, 558 }, 559 ] 560 function_data = {"args": function_args} 561 func = ir_data_utils.IrDataSerializer.from_dict( 562 ir_data.Function, function_data 563 ) 564 self.assertIsNotNone(func) 565 566 def test_ir_data_serializer_copy_from_dict(self): 567 """Tests that updating an IR data struct from a dict works properly""" 568 attribute = ir_data.Attribute( 569 value=ir_data.AttributeValue(expression=ir_data.Expression()), 570 name=ir_data.Word(text="phil"), 571 ) 572 serializer = ir_data_utils.IrDataSerializer(attribute) 573 raw_dict = serializer.to_dict(exclude_none=False) 574 575 new_attribute = ir_data.Attribute() 576 new_serializer = ir_data_utils.IrDataSerializer(new_attribute) 577 new_serializer.copy_from_dict(raw_dict) 578 self.assertEqual(attribute, new_attribute) 579 580 581class ReadOnlyFieldCheckerTest(unittest.TestCase): 582 """Tests the ReadOnlyFieldChecker""" 583 584 def test_basic_wrapper(self): 585 """Tests basic field checker actions""" 586 union = ClassWithTwoUnions( 587 opaque=Opaque(), boolean=True, non_union_field=10 588 ) 589 field_checker = ir_data_utils.reader(union) 590 591 # All accesses should return a wrapper object 592 self.assertIsNotNone(field_checker.opaque) 593 self.assertIsNotNone(field_checker.integer) 594 self.assertIsNotNone(field_checker.boolean) 595 self.assertIsNotNone(field_checker.enumeration) 596 self.assertIsNotNone(field_checker.non_union_field) 597 # Scalar field should pass through 598 self.assertEqual(field_checker.non_union_field, 10) 599 600 # Make sure HasField works 601 self.assertTrue(field_checker.HasField("opaque")) 602 self.assertFalse(field_checker.HasField("integer")) 603 self.assertTrue(field_checker.HasField("boolean")) 604 self.assertFalse(field_checker.HasField("enumeration")) 605 self.assertTrue(field_checker.HasField("non_union_field")) 606 607 def test_construct_from_field_checker(self): 608 """Tests that constructing from another field checker works""" 609 union = ClassWithTwoUnions( 610 opaque=Opaque(), boolean=True, non_union_field=10 611 ) 612 field_checker_orig = ir_data_utils.reader(union) 613 field_checker = ir_data_utils.reader(field_checker_orig) 614 self.assertIsNotNone(field_checker) 615 self.assertEqual(field_checker.ir_or_spec, union) 616 617 # All accesses should return a wrapper object 618 self.assertIsNotNone(field_checker.opaque) 619 self.assertIsNotNone(field_checker.integer) 620 self.assertIsNotNone(field_checker.boolean) 621 self.assertIsNotNone(field_checker.enumeration) 622 self.assertIsNotNone(field_checker.non_union_field) 623 # Scalar field should pass through 624 self.assertEqual(field_checker.non_union_field, 10) 625 626 # Make sure HasField works 627 self.assertTrue(field_checker.HasField("opaque")) 628 self.assertFalse(field_checker.HasField("integer")) 629 self.assertTrue(field_checker.HasField("boolean")) 630 self.assertFalse(field_checker.HasField("enumeration")) 631 self.assertTrue(field_checker.HasField("non_union_field")) 632 633 def test_read_only(self) -> None: 634 """Tests that the read only wrapper really is read only""" 635 union = ClassWithTwoUnions( 636 opaque=Opaque(), boolean=True, non_union_field=10 637 ) 638 field_checker = ir_data_utils.reader(union) 639 640 def set_field(): 641 field_checker.opaque = None 642 643 self.assertRaises(AttributeError, set_field) 644 645 646ir_data_fields.cache_message_specs( 647 sys.modules[ReadOnlyFieldCheckerTest.__module__], ir_data.Message) 648 649if __name__ == "__main__": 650 unittest.main() 651