xref: /aosp_15_r20/external/emboss/compiler/util/ir_data_utils_test.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
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