xref: /aosp_15_r20/external/emboss/compiler/back_end/cpp/header_generator_test.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1# Copyright 2023 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
17import unittest
18from compiler.back_end.cpp import header_generator
19from compiler.front_end import glue
20from compiler.util import error
21from compiler.util import ir_data
22from compiler.util import ir_data_utils
23from compiler.util import test_util
24
25def _make_ir_from_emb(emb_text, name="m.emb"):
26  ir, unused_debug_info, errors = glue.parse_emboss_file(
27      name,
28      test_util.dict_file_reader({name: emb_text}))
29  assert not errors
30  return ir
31
32
33class NormalizeIrTest(unittest.TestCase):
34
35  def test_accepts_string_attribute(self):
36    ir = _make_ir_from_emb('[(cpp) namespace: "foo"]\n')
37    self.assertEqual([], header_generator.generate_header(ir)[1])
38
39  def test_rejects_wrong_type_for_string_attribute(self):
40    ir = _make_ir_from_emb("[(cpp) namespace: 9]\n")
41    attr = ir.module[0].attribute[0]
42    self.assertEqual([[
43        error.error("m.emb", attr.value.source_location,
44                    "Attribute '(cpp) namespace' must have a string value.")
45    ]], header_generator.generate_header(ir)[1])
46
47  def test_rejects_emboss_internal_attribute_with_back_end_specifier(self):
48    ir = _make_ir_from_emb('[(cpp) byte_order: "LittleEndian"]\n')
49    attr = ir.module[0].attribute[0]
50    self.assertEqual([[
51        error.error("m.emb", attr.name.source_location,
52                    "Unknown attribute '(cpp) byte_order' on module 'm.emb'.")
53    ]], header_generator.generate_header(ir)[1])
54
55  def test_accepts_enum_case(self):
56    mod_ir = _make_ir_from_emb('[(cpp) $default enum_case: "kCamelCase"]')
57    self.assertEqual([], header_generator.generate_header(mod_ir)[1])
58    enum_ir = _make_ir_from_emb('enum Foo:\n'
59                                '  [(cpp) $default enum_case: "kCamelCase"]\n'
60                                '  BAR = 1\n'
61                                '  BAZ = 2\n')
62    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
63    enum_value_ir = _make_ir_from_emb('enum Foo:\n'
64                                      '  BAR = 1  [(cpp) enum_case: "kCamelCase"]\n'
65                                      '  BAZ = 2\n'
66                                      '    [(cpp) enum_case: "kCamelCase"]\n')
67    self.assertEqual([], header_generator.generate_header(enum_value_ir)[1])
68    enum_in_struct_ir = _make_ir_from_emb('struct Outer:\n'
69                                          '  [(cpp) $default enum_case: "kCamelCase"]\n'
70                                          '  enum Inner:\n'
71                                          '    BAR = 1\n'
72                                          '    BAZ = 2\n')
73    self.assertEqual([], header_generator.generate_header(enum_in_struct_ir)[1])
74    enum_in_bits_ir = _make_ir_from_emb('bits Outer:\n'
75                                        '  [(cpp) $default enum_case: "kCamelCase"]\n'
76                                        '  enum Inner:\n'
77                                        '    BAR = 1\n'
78                                        '    BAZ = 2\n')
79    self.assertEqual([], header_generator.generate_header(enum_in_bits_ir)[1])
80    enum_ir = _make_ir_from_emb('enum Foo:\n'
81                                '  [(cpp) $default enum_case: "SHOUTY_CASE,"]\n'
82                                '  BAR = 1\n'
83                                '  BAZ = 2\n')
84    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
85    enum_ir = _make_ir_from_emb('enum Foo:\n'
86                                '  [(cpp) $default enum_case: "SHOUTY_CASE   ,kCamelCase"]\n'
87                                '  BAR = 1\n'
88                                '  BAZ = 2\n')
89    self.assertEqual([], header_generator.generate_header(enum_ir)[1])
90
91  def test_rejects_bad_enum_case_at_start(self):
92    ir = _make_ir_from_emb('enum Foo:\n'
93                           '  [(cpp) $default enum_case: "SHORTY_CASE, kCamelCase"]\n'
94                           '  BAR = 1\n'
95                           '  BAZ = 2\n')
96    attr = ir.module[0].type[0].attribute[0]
97
98    bad_case_source_location = ir_data.Location()
99    bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
100    bad_case_source_location.CopyFrom(attr.value.source_location)
101    # Location of SHORTY_CASE in the attribute line.
102    bad_case_source_location.start.column = 30
103    bad_case_source_location.end.column = 41
104
105    self.assertEqual([[
106        error.error("m.emb", bad_case_source_location,
107                    'Unsupported enum case "SHORTY_CASE", '
108                    'supported cases are: SHOUTY_CASE, kCamelCase.')
109    ]], header_generator.generate_header(ir)[1])
110
111  def test_rejects_bad_enum_case_in_middle(self):
112    ir = _make_ir_from_emb('enum Foo:\n'
113                           '  [(cpp) $default enum_case: "SHOUTY_CASE, bad_CASE, kCamelCase"]\n'
114                           '  BAR = 1\n'
115                           '  BAZ = 2\n')
116    attr = ir.module[0].type[0].attribute[0]
117
118    bad_case_source_location = ir_data.Location()
119    bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
120    bad_case_source_location.CopyFrom(attr.value.source_location)
121    # Location of bad_CASE in the attribute line.
122    bad_case_source_location.start.column = 43
123    bad_case_source_location.end.column = 51
124
125    self.assertEqual([[
126        error.error("m.emb", bad_case_source_location,
127                    'Unsupported enum case "bad_CASE", '
128                    'supported cases are: SHOUTY_CASE, kCamelCase.')
129    ]], header_generator.generate_header(ir)[1])
130
131  def test_rejects_bad_enum_case_at_end(self):
132    ir = _make_ir_from_emb('enum Foo:\n'
133                           '  [(cpp) $default enum_case: "SHOUTY_CASE, kCamelCase, BAD_case"]\n'
134                           '  BAR = 1\n'
135                           '  BAZ = 2\n')
136    attr = ir.module[0].type[0].attribute[0]
137
138    bad_case_source_location = ir_data.Location()
139    bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
140    bad_case_source_location.CopyFrom(attr.value.source_location)
141    # Location of BAD_case in the attribute line.
142    bad_case_source_location.start.column = 55
143    bad_case_source_location.end.column = 63
144
145    self.assertEqual([[
146        error.error("m.emb", bad_case_source_location,
147                    'Unsupported enum case "BAD_case", '
148                    'supported cases are: SHOUTY_CASE, kCamelCase.')
149    ]], header_generator.generate_header(ir)[1])
150
151  def test_rejects_duplicate_enum_case(self):
152    ir = _make_ir_from_emb('enum Foo:\n'
153                           '  [(cpp) $default enum_case: "SHOUTY_CASE, SHOUTY_CASE"]\n'
154                           '  BAR = 1\n'
155                           '  BAZ = 2\n')
156    attr = ir.module[0].type[0].attribute[0]
157
158    bad_case_source_location = ir_data.Location()
159    bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
160    bad_case_source_location.CopyFrom(attr.value.source_location)
161    # Location of the second SHOUTY_CASE in the attribute line.
162    bad_case_source_location.start.column = 43
163    bad_case_source_location.end.column = 54
164
165    self.assertEqual([[
166        error.error("m.emb", bad_case_source_location,
167                    'Duplicate enum case "SHOUTY_CASE".')
168    ]], header_generator.generate_header(ir)[1])
169
170
171  def test_rejects_empty_enum_case(self):
172    # Double comma
173    ir = _make_ir_from_emb('enum Foo:\n'
174                           '  [(cpp) $default enum_case: "SHOUTY_CASE,, kCamelCase"]\n'
175                           '  BAR = 1\n'
176                           '  BAZ = 2\n')
177    attr = ir.module[0].type[0].attribute[0]
178
179    bad_case_source_location = ir_data.Location()
180    bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
181    bad_case_source_location.CopyFrom(attr.value.source_location)
182    # Location of excess comma.
183    bad_case_source_location.start.column = 42
184    bad_case_source_location.end.column = 42
185
186    self.assertEqual([[
187        error.error("m.emb", bad_case_source_location,
188                    'Empty enum case (or excess comma).')
189    ]], header_generator.generate_header(ir)[1])
190
191    # Leading comma
192    ir = _make_ir_from_emb('enum Foo:\n'
193                           '  [(cpp) $default enum_case: ", SHOUTY_CASE, kCamelCase"]\n'
194                           '  BAR = 1\n'
195                           '  BAZ = 2\n')
196
197    bad_case_source_location.start.column = 30
198    bad_case_source_location.end.column = 30
199
200    self.assertEqual([[
201        error.error("m.emb", bad_case_source_location,
202                    'Empty enum case (or excess comma).')
203    ]], header_generator.generate_header(ir)[1])
204
205    # Excess trailing comma
206    ir = _make_ir_from_emb('enum Foo:\n'
207                           '  [(cpp) $default enum_case: "SHOUTY_CASE, kCamelCase,,"]\n'
208                           '  BAR = 1\n'
209                           '  BAZ = 2\n')
210
211    bad_case_source_location.start.column = 54
212    bad_case_source_location.end.column = 54
213
214    self.assertEqual([[
215        error.error("m.emb", bad_case_source_location,
216                    'Empty enum case (or excess comma).')
217    ]], header_generator.generate_header(ir)[1])
218
219    # Whitespace enum case
220    ir = _make_ir_from_emb('enum Foo:\n'
221                           '  [(cpp) $default enum_case: "SHOUTY_CASE,   , kCamelCase"]\n'
222                           '  BAR = 1\n'
223                           '  BAZ = 2\n')
224
225    bad_case_source_location.start.column = 45
226    bad_case_source_location.end.column = 45
227
228    self.assertEqual([[
229        error.error("m.emb", bad_case_source_location,
230                    'Empty enum case (or excess comma).')
231    ]], header_generator.generate_header(ir)[1])
232
233    # Empty enum_case string
234    ir = _make_ir_from_emb('enum Foo:\n'
235                           '  [(cpp) $default enum_case: ""]\n'
236                           '  BAR = 1\n'
237                           '  BAZ = 2\n')
238
239    bad_case_source_location.start.column = 30
240    bad_case_source_location.end.column = 30
241
242    self.assertEqual([[
243        error.error("m.emb", bad_case_source_location,
244                    'Empty enum case (or excess comma).')
245    ]], header_generator.generate_header(ir)[1])
246
247    # Whitespace enum_case string
248    ir = _make_ir_from_emb('enum Foo:\n'
249                           '  [(cpp) $default enum_case: "     "]\n'
250                           '  BAR = 1\n'
251                           '  BAZ = 2\n')
252
253    bad_case_source_location.start.column = 35
254    bad_case_source_location.end.column = 35
255
256    self.assertEqual([[
257        error.error("m.emb", bad_case_source_location,
258                    'Empty enum case (or excess comma).')
259    ]], header_generator.generate_header(ir)[1])
260
261    # One-character whitespace enum_case string
262    ir = _make_ir_from_emb('enum Foo:\n'
263                           '  [(cpp) $default enum_case: " "]\n'
264                           '  BAR = 1\n'
265                           '  BAZ = 2\n')
266
267    bad_case_source_location.start.column = 31
268    bad_case_source_location.end.column = 31
269
270    self.assertEqual([[
271        error.error("m.emb", bad_case_source_location,
272                    'Empty enum case (or excess comma).')
273    ]], header_generator.generate_header(ir)[1])
274
275  def test_accepts_namespace(self):
276    for test in [
277        '[(cpp) namespace: "basic"]\n',
278        '[(cpp) namespace: "multiple::components"]\n',
279        '[(cpp) namespace: "::absolute"]\n',
280        '[(cpp) namespace: "::fully::qualified"]\n',
281        '[(cpp) namespace: "CAN::Be::cAPITAL"]\n',
282        '[(cpp) namespace: "trailingNumbers54321"]\n',
283        '[(cpp) namespace: "containing4321numbers"]\n',
284        '[(cpp) namespace: "can_have_underscores"]\n',
285        '[(cpp) namespace: "_initial_underscore"]\n',
286        '[(cpp) namespace: "_initial::_underscore"]\n',
287        '[(cpp) namespace: "::_initial::_underscore"]\n',
288        '[(cpp) namespace: "trailing_underscore_"]\n',
289        '[(cpp) namespace: "trailing_::underscore_"]\n',
290        '[(cpp) namespace: "::trailing_::underscore_"]\n',
291        '[(cpp) namespace: " spaces "]\n',
292        '[(cpp) namespace: "with :: spaces"]\n',
293        '[(cpp) namespace: "   ::fully:: qualified :: with::spaces"]\n',
294    ]:
295      ir = _make_ir_from_emb(test)
296      self.assertEqual([], header_generator.generate_header(ir)[1])
297
298  def test_rejects_non_namespace_strings(self):
299    for test in [
300        '[(cpp) namespace: "5th::avenue"]\n',
301        '[(cpp) namespace: "can\'t::have::apostrophe"]\n',
302        '[(cpp) namespace: "cannot-have-dash"]\n',
303        '[(cpp) namespace: "no/slashes"]\n',
304        '[(cpp) namespace: "no\\\\slashes"]\n',
305        '[(cpp) namespace: "apostrophes*are*rejected"]\n',
306        '[(cpp) namespace: "avoid.dot"]\n',
307        '[(cpp) namespace: "does5+5"]\n',
308        '[(cpp) namespace: "=10"]\n',
309        '[(cpp) namespace: "?"]\n',
310        '[(cpp) namespace: "reject::spaces in::components"]\n',
311        '[(cpp) namespace: "totally::valid::but::extra         +"]\n',
312        '[(cpp) namespace: "totally::valid::but::extra         ::?"]\n',
313        '[(cpp) namespace: "< totally::valid::but::extra"]\n',
314        '[(cpp) namespace: "< ::totally::valid::but::extra"]\n',
315        '[(cpp) namespace: "::totally::valid::but::extra::"]\n',
316        '[(cpp) namespace: ":::extra::colon"]\n',
317        '[(cpp) namespace: "::extra:::colon"]\n',
318    ]:
319      ir = _make_ir_from_emb(test)
320      attr = ir.module[0].attribute[0]
321      self.assertEqual([[
322          error.error("m.emb", attr.value.source_location,
323                      'Invalid namespace, must be a valid C++ namespace, such '
324                      'as "abc", "abc::def", or "::abc::def::ghi" (ISO/IEC '
325                      '14882:2017 enclosing-namespace-specifier).')
326      ]], header_generator.generate_header(ir)[1])
327
328  def test_rejects_empty_namespace(self):
329    for test in [
330        '[(cpp) namespace: ""]\n',
331        '[(cpp) namespace: " "]\n',
332        '[(cpp) namespace: "    "]\n',
333    ]:
334      ir = _make_ir_from_emb(test)
335      attr = ir.module[0].attribute[0]
336      self.assertEqual([[
337          error.error("m.emb", attr.value.source_location,
338                      'Empty namespace value is not allowed.')
339      ]], header_generator.generate_header(ir)[1])
340
341  def test_rejects_global_namespace(self):
342    for test in [
343        '[(cpp) namespace: "::"]\n',
344        '[(cpp) namespace: "  ::"]\n',
345        '[(cpp) namespace: ":: "]\n',
346        '[(cpp) namespace: " ::  "]\n',
347    ]:
348      ir = _make_ir_from_emb(test)
349      attr = ir.module[0].attribute[0]
350      self.assertEqual([[
351          error.error("m.emb", attr.value.source_location,
352                      'Global namespace is not allowed.')
353      ]], header_generator.generate_header(ir)[1])
354
355  def test_rejects_reserved_namespace(self):
356    for test, expected in [
357        # Only component
358        ('[(cpp) namespace: "class"]\n', 'class'),
359        # Only component, fully qualified name
360        ('[(cpp) namespace: "::const"]\n', 'const'),
361        # First component
362        ('[(cpp) namespace: "if::valid"]\n', 'if'),
363        # First component, fully qualified name
364        ('[(cpp) namespace: "::auto::pilot"]\n', 'auto'),
365        # Last component
366        ('[(cpp) namespace: "make::do"]\n', 'do'),
367        # Middle component
368        ('[(cpp) namespace: "our::new::product"]\n', 'new'),
369    ]:
370      ir = _make_ir_from_emb(test)
371      attr = ir.module[0].attribute[0]
372
373      self.assertEqual([[
374          error.error("m.emb", attr.value.source_location,
375                      f'Reserved word "{expected}" is not allowed '
376                      f'as a namespace component.')]],
377          header_generator.generate_header(ir)[1])
378
379
380if __name__ == "__main__":
381    unittest.main()
382