gkdbus: Fix underflow and unreachable code bug
[platform/upstream/glib.git] / gobject / tests / mkenums.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright © 2018 Endless Mobile, Inc.
5 #
6 # SPDX-License-Identifier: LGPL-2.1-or-later
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 # MA  02110-1301  USA
22
23 """Integration tests for glib-mkenums utility."""
24
25 import collections
26 import os
27 import shutil
28 import subprocess
29 import sys
30 import tempfile
31 import textwrap
32 import unittest
33
34 import taptestrunner
35
36
37 Result = collections.namedtuple("Result", ("info", "out", "err", "subs"))
38
39
40 class TestMkenums(unittest.TestCase):
41     """Integration test for running glib-mkenums.
42
43     This can be run when installed or uninstalled. When uninstalled, it
44     requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set.
45
46     The idea with this test harness is to test the glib-mkenums utility, its
47     handling of command line arguments, its exit statuses, and its handling of
48     various C source codes. In future we could split the core glib-mkenums
49     parsing and generation code out into a library and unit test that, and
50     convert this test to just check command line behaviour.
51     """
52
53     # Track the cwd, we want to back out to that to clean up our tempdir
54     cwd = ""
55     rspfile = False
56
57     def setUp(self):
58         self.timeout_seconds = 10  # seconds per test
59         self.tmpdir = tempfile.TemporaryDirectory()
60         self.cwd = os.getcwd()
61         os.chdir(self.tmpdir.name)
62         print("tmpdir:", self.tmpdir.name)
63         if "G_TEST_BUILDDIR" in os.environ:
64             self.__mkenums = os.path.join(
65                 os.environ["G_TEST_BUILDDIR"], "..", "glib-mkenums"
66             )
67         else:
68             self.__mkenums = shutil.which("glib-mkenums")
69         print("rspfile: {}, mkenums:".format(self.rspfile), self.__mkenums)
70
71     def tearDown(self):
72         os.chdir(self.cwd)
73         self.tmpdir.cleanup()
74
75     def _write_rspfile(self, argv):
76         import shlex
77
78         with tempfile.NamedTemporaryFile(
79             dir=self.tmpdir.name, mode="w", delete=False
80         ) as f:
81             contents = " ".join([shlex.quote(arg) for arg in argv])
82             print("Response file contains:", contents)
83             f.write(contents)
84             f.flush()
85             return f.name
86
87     def runMkenums(self, *args):
88         if self.rspfile:
89             rspfile = self._write_rspfile(args)
90             args = ["@" + rspfile]
91         argv = [self.__mkenums]
92
93         # shebang lines are not supported on native
94         # Windows consoles
95         if os.name == "nt":
96             argv.insert(0, sys.executable)
97
98         argv.extend(args)
99         print("Running:", argv)
100
101         env = os.environ.copy()
102         env["LC_ALL"] = "C.UTF-8"
103         print("Environment:", env)
104
105         # We want to ensure consistent line endings...
106         info = subprocess.run(
107             argv,
108             timeout=self.timeout_seconds,
109             stdout=subprocess.PIPE,
110             stderr=subprocess.PIPE,
111             env=env,
112             universal_newlines=True,
113         )
114         info.check_returncode()
115         out = info.stdout.strip()
116         err = info.stderr.strip()
117
118         # Known substitutions for standard boilerplate
119         subs = {
120             "standard_top_comment": "This file is generated by glib-mkenums, do not modify "
121             "it. This code is licensed under the same license as the "
122             "containing project. Note that it links to GLib, so must "
123             "comply with the LGPL linking clauses.",
124             "standard_bottom_comment": "Generated data ends here",
125         }
126
127         result = Result(info, out, err, subs)
128
129         print("Output:", result.out)
130         return result
131
132     def runMkenumsWithTemplate(self, template_contents, *args):
133         with tempfile.NamedTemporaryFile(
134             dir=self.tmpdir.name, suffix=".template", delete=False
135         ) as template_file:
136             # Write out the template.
137             template_file.write(template_contents.encode("utf-8"))
138             print(template_file.name + ":", template_contents)
139             template_file.flush()
140
141             return self.runMkenums("--template", template_file.name, *args)
142
143     def runMkenumsWithAllSubstitutions(self, *args):
144         """Run glib-mkenums with a template which outputs all substitutions."""
145         template_contents = """
146 /*** BEGIN file-header ***/
147 file-header
148 /*** END file-header ***/
149
150 /*** BEGIN file-production ***/
151 file-production
152 filename: @filename@
153 basename: @basename@
154 /*** END file-production ***/
155
156 /*** BEGIN enumeration-production ***/
157 enumeration-production
158 EnumName: @EnumName@
159 enum_name: @enum_name@
160 ENUMNAME: @ENUMNAME@
161 ENUMSHORT: @ENUMSHORT@
162 ENUMPREFIX: @ENUMPREFIX@
163 enumsince: @enumsince@
164 type: @type@
165 Type: @Type@
166 TYPE: @TYPE@
167 /*** END enumeration-production ***/
168
169 /*** BEGIN value-header ***/
170 value-header
171 EnumName: @EnumName@
172 enum_name: @enum_name@
173 ENUMNAME: @ENUMNAME@
174 ENUMSHORT: @ENUMSHORT@
175 ENUMPREFIX: @ENUMPREFIX@
176 enumsince: @enumsince@
177 type: @type@
178 Type: @Type@
179 TYPE: @TYPE@
180 /*** END value-header ***/
181
182 /*** BEGIN value-production ***/
183 value-production
184 VALUENAME: @VALUENAME@
185 valuenick: @valuenick@
186 valuenum: @valuenum@
187 type: @type@
188 Type: @Type@
189 TYPE: @TYPE@
190 /*** END value-production ***/
191
192 /*** BEGIN value-tail ***/
193 value-tail
194 EnumName: @EnumName@
195 enum_name: @enum_name@
196 ENUMNAME: @ENUMNAME@
197 ENUMSHORT: @ENUMSHORT@
198 ENUMPREFIX: @ENUMPREFIX@
199 enumsince: @enumsince@
200 type: @type@
201 Type: @Type@
202 TYPE: @TYPE@
203 /*** END value-tail ***/
204
205 /*** BEGIN comment ***/
206 comment
207 comment: @comment@
208 /*** END comment ***/
209
210 /*** BEGIN file-tail ***/
211 file-tail
212 /*** END file-tail ***/
213 """
214         return self.runMkenumsWithTemplate(template_contents, *args)
215
216     def runMkenumsWithHeader(self, h_contents, encoding="utf-8"):
217         with tempfile.NamedTemporaryFile(
218             dir=self.tmpdir.name, suffix=".h", delete=False
219         ) as h_file:
220             # Write out the header to be scanned.
221             h_file.write(h_contents.encode(encoding))
222             print(h_file.name + ":", h_contents)
223             h_file.flush()
224
225             # Run glib-mkenums with a template which outputs all substitutions.
226             result = self.runMkenumsWithAllSubstitutions(h_file.name)
227
228             # Known substitutions for generated filenames.
229             result.subs.update(
230                 {"filename": h_file.name, "basename": os.path.basename(h_file.name)}
231             )
232
233             return result
234
235     def assertSingleEnum(
236         self,
237         result,
238         enum_name_camel,
239         enum_name_lower,
240         enum_name_upper,
241         enum_name_short,
242         enum_prefix,
243         enum_since,
244         type_lower,
245         type_camel,
246         type_upper,
247         value_name,
248         value_nick,
249         value_num,
250     ):
251         """Assert that out (from runMkenumsWithHeader()) contains a single
252         enum and value matching the given arguments."""
253         subs = dict(
254             {
255                 "enum_name_camel": enum_name_camel,
256                 "enum_name_lower": enum_name_lower,
257                 "enum_name_upper": enum_name_upper,
258                 "enum_name_short": enum_name_short,
259                 "enum_prefix": enum_prefix,
260                 "enum_since": enum_since,
261                 "type_lower": type_lower,
262                 "type_camel": type_camel,
263                 "type_upper": type_upper,
264                 "value_name": value_name,
265                 "value_nick": value_nick,
266                 "value_num": value_num,
267             },
268             **result.subs
269         )
270
271         self.assertEqual(
272             """
273 comment
274 comment: {standard_top_comment}
275
276
277 file-header
278 file-production
279 filename: {filename}
280 basename: {basename}
281 enumeration-production
282 EnumName: {enum_name_camel}
283 enum_name: {enum_name_lower}
284 ENUMNAME: {enum_name_upper}
285 ENUMSHORT: {enum_name_short}
286 ENUMPREFIX: {enum_prefix}
287 enumsince: {enum_since}
288 type: {type_lower}
289 Type: {type_camel}
290 TYPE: {type_upper}
291 value-header
292 EnumName: {enum_name_camel}
293 enum_name: {enum_name_lower}
294 ENUMNAME: {enum_name_upper}
295 ENUMSHORT: {enum_name_short}
296 ENUMPREFIX: {enum_prefix}
297 enumsince: {enum_since}
298 type: {type_lower}
299 Type: {type_camel}
300 TYPE: {type_upper}
301 value-production
302 VALUENAME: {value_name}
303 valuenick: {value_nick}
304 valuenum: {value_num}
305 type: {type_lower}
306 Type: {type_camel}
307 TYPE: {type_upper}
308 value-tail
309 EnumName: {enum_name_camel}
310 enum_name: {enum_name_lower}
311 ENUMNAME: {enum_name_upper}
312 ENUMSHORT: {enum_name_short}
313 ENUMPREFIX: {enum_prefix}
314 enumsince: {enum_since}
315 type: {type_lower}
316 Type: {type_camel}
317 TYPE: {type_upper}
318 file-tail
319
320 comment
321 comment: {standard_bottom_comment}
322 """.format(
323                 **subs
324             ).strip(),
325             result.out,
326         )
327
328     def test_help(self):
329         """Test the --help argument."""
330         result = self.runMkenums("--help")
331         self.assertIn("usage: glib-mkenums", result.out)
332
333     def test_no_args(self):
334         """Test running with no arguments at all."""
335         result = self.runMkenums()
336         self.assertEqual("", result.err)
337         self.assertEqual(
338             """/* {standard_top_comment} */
339
340
341 /* {standard_bottom_comment} */""".format(
342                 **result.subs
343             ),
344             result.out.strip(),
345         )
346
347     def test_empty_template(self):
348         """Test running with an empty template and no header files."""
349         result = self.runMkenumsWithTemplate("")
350         self.assertEqual("", result.err)
351         self.assertEqual(
352             """/* {standard_top_comment} */
353
354
355 /* {standard_bottom_comment} */""".format(
356                 **result.subs
357             ),
358             result.out.strip(),
359         )
360
361     def test_no_headers(self):
362         """Test running with a complete template, but no header files."""
363         result = self.runMkenumsWithAllSubstitutions()
364         self.assertEqual("", result.err)
365         self.assertEqual(
366             """
367 comment
368 comment: {standard_top_comment}
369
370
371 file-header
372 file-tail
373
374 comment
375 comment: {standard_bottom_comment}
376 """.format(
377                 **result.subs
378             ).strip(),
379             result.out,
380         )
381
382     def test_empty_header(self):
383         """Test an empty header."""
384         result = self.runMkenumsWithHeader("")
385         self.assertEqual("", result.err)
386         self.assertEqual(
387             """
388 comment
389 comment: {standard_top_comment}
390
391
392 file-header
393 file-tail
394
395 comment
396 comment: {standard_bottom_comment}
397 """.format(
398                 **result.subs
399             ).strip(),
400             result.out,
401         )
402
403     def test_enum_name(self):
404         """Test typedefs with an enum and a typedef name. Bug #794506."""
405         h_contents = """
406         typedef enum _SomeEnumIdentifier {
407           ENUM_VALUE
408         } SomeEnumIdentifier;
409         """
410         result = self.runMkenumsWithHeader(h_contents)
411         self.assertEqual("", result.err)
412         self.assertSingleEnum(
413             result,
414             "SomeEnumIdentifier",
415             "some_enum_identifier",
416             "SOME_ENUM_IDENTIFIER",
417             "ENUM_IDENTIFIER",
418             "SOME",
419             "",
420             "enum",
421             "Enum",
422             "ENUM",
423             "ENUM_VALUE",
424             "value",
425             "0",
426         )
427
428     def test_non_utf8_encoding(self):
429         """Test source files with non-UTF-8 encoding. Bug #785113."""
430         h_contents = """
431         /* Copyright © La Peña */
432         typedef enum {
433           ENUM_VALUE
434         } SomeEnumIdentifier;
435         """
436         result = self.runMkenumsWithHeader(h_contents, encoding="iso-8859-1")
437         self.assertIn("WARNING: UnicodeWarning: ", result.err)
438         self.assertSingleEnum(
439             result,
440             "SomeEnumIdentifier",
441             "some_enum_identifier",
442             "SOME_ENUM_IDENTIFIER",
443             "ENUM_IDENTIFIER",
444             "SOME",
445             "",
446             "enum",
447             "Enum",
448             "ENUM",
449             "ENUM_VALUE",
450             "value",
451             "0",
452         )
453
454     def test_reproducible(self):
455         """Test builds are reproducible regardless of file ordering.
456         Bug #691436."""
457         template_contents = "template"
458
459         h_contents1 = """
460         typedef enum {
461           FIRST,
462         } Header1;
463         """
464
465         h_contents2 = """
466         typedef enum {
467           SECOND,
468         } Header2;
469         """
470
471         with tempfile.NamedTemporaryFile(
472             dir=self.tmpdir.name, suffix="1.h", delete=False
473         ) as h_file1, tempfile.NamedTemporaryFile(
474             dir=self.tmpdir.name, suffix="2.h", delete=False
475         ) as h_file2:
476             # Write out the headers.
477             h_file1.write(h_contents1.encode("utf-8"))
478             h_file2.write(h_contents2.encode("utf-8"))
479
480             h_file1.flush()
481             h_file2.flush()
482
483             # Run glib-mkenums with the headers in one order, and then again
484             # in another order.
485             result1 = self.runMkenumsWithTemplate(
486                 template_contents, h_file1.name, h_file2.name
487             )
488             self.assertEqual("", result1.err)
489
490             result2 = self.runMkenumsWithTemplate(
491                 template_contents, h_file2.name, h_file1.name
492             )
493             self.assertEqual("", result2.err)
494
495             # The output should be the same.
496             self.assertEqual(result1.out, result2.out)
497
498     def test_no_nick(self):
499         """Test trigraphs with a desc but no nick. Issue #1360."""
500         h_contents = """
501         typedef enum {
502           GEGL_SAMPLER_NEAREST = 0,   /*< desc="nearest"      >*/
503         } GeglSamplerType;
504         """
505         result = self.runMkenumsWithHeader(h_contents)
506         self.assertEqual("", result.err)
507         self.assertSingleEnum(
508             result,
509             "GeglSamplerType",
510             "gegl_sampler_type",
511             "GEGL_SAMPLER_TYPE",
512             "SAMPLER_TYPE",
513             "GEGL",
514             "",
515             "enum",
516             "Enum",
517             "ENUM",
518             "GEGL_SAMPLER_NEAREST",
519             "nearest",
520             "0",
521         )
522
523     def test_with_double_quotes(self):
524         """Test trigraphs with double-quoted expressions. Issue #65."""
525         h_contents = """
526         typedef enum {
527           FOO_VALUE /*< nick="eek, a comma" >*/
528         } Foo;
529         """
530         result = self.runMkenumsWithHeader(h_contents)
531         self.assertEqual("", result.err)
532         self.assertSingleEnum(
533             result,
534             "Foo",
535             "foo_",
536             "FOO_",
537             "",
538             "FOO",
539             "",
540             "enum",
541             "Enum",
542             "ENUM",
543             "FOO_VALUE",
544             "eek, a comma",
545             "0",
546         )
547
548     def test_filename_basename_in_fhead_ftail(self):
549         template_contents = """
550 /*** BEGIN file-header ***/
551 file-header
552 filename: @filename@
553 basename: @basename@
554 /*** END file-header ***/
555
556 /*** BEGIN comment ***/
557 comment
558 comment: @comment@
559 /*** END comment ***/
560
561 /*** BEGIN file-tail ***/
562 file-tail
563 filename: @filename@
564 basename: @basename@
565 /*** END file-tail ***/"""
566         result = self.runMkenumsWithTemplate(template_contents)
567         self.assertEqual(
568             textwrap.dedent(
569                 """
570                 WARNING: @filename@ used in file-header section.
571                 WARNING: @basename@ used in file-header section.
572                 WARNING: @filename@ used in file-tail section.
573                 WARNING: @basename@ used in file-tail section.
574                 """
575             ).strip(),
576             result.err,
577         )
578         self.assertEqual(
579             """
580 comment
581 comment: {standard_top_comment}
582
583
584 file-header
585 filename: @filename@
586 basename: @basename@
587 file-tail
588 filename: @filename@
589 basename: @basename@
590
591 comment
592 comment: {standard_bottom_comment}
593 """.format(
594                 **result.subs
595             ).strip(),
596             result.out,
597         )
598
599     def test_since(self):
600         """Test user-provided 'since' version handling
601         https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1492"""
602         h_contents = """
603         typedef enum { /*< since=1.0 >*/
604             QMI_WMS_MESSAGE_PROTOCOL_CDMA = 0,
605         } QmiWmsMessageProtocol;
606         """
607         result = self.runMkenumsWithHeader(h_contents)
608         self.assertEqual("", result.err)
609         self.assertSingleEnum(
610             result,
611             "QmiWmsMessageProtocol",
612             "qmi_wms_message_protocol",
613             "QMI_WMS_MESSAGE_PROTOCOL",
614             "WMS_MESSAGE_PROTOCOL",
615             "QMI",
616             "1.0",
617             "enum",
618             "Enum",
619             "ENUM",
620             "QMI_WMS_MESSAGE_PROTOCOL_CDMA",
621             "cdma",
622             "0",
623         )
624
625     def test_enum_private_public(self):
626         """Test private/public enums. Bug #782162."""
627         h_contents1 = """
628         typedef enum {
629           ENUM_VALUE_PUBLIC1,
630           /*< private >*/
631           ENUM_VALUE_PRIVATE,
632         } SomeEnumA
633         """
634
635         h_contents2 = """
636         typedef enum {
637           /*< private >*/
638           ENUM_VALUE_PRIVATE,
639           /*< public >*/
640           ENUM_VALUE_PUBLIC2,
641         } SomeEnumB;
642         """
643
644         result = self.runMkenumsWithHeader(h_contents1)
645         self.maxDiff = None
646         self.assertEqual("", result.err)
647         self.assertSingleEnum(
648             result,
649             "SomeEnumA",
650             "some_enum_a",
651             "SOME_ENUM_A",
652             "ENUM_A",
653             "SOME",
654             "",
655             "enum",
656             "Enum",
657             "ENUM",
658             "ENUM_VALUE_PUBLIC1",
659             "public1",
660             "0",
661         )
662         result = self.runMkenumsWithHeader(h_contents2)
663         self.assertEqual("", result.err)
664         self.assertSingleEnum(
665             result,
666             "SomeEnumB",
667             "some_enum_b",
668             "SOME_ENUM_B",
669             "ENUM_B",
670             "SOME",
671             "",
672             "enum",
673             "Enum",
674             "ENUM",
675             "ENUM_VALUE_PUBLIC2",
676             "public2",
677             "1",
678         )
679
680     def test_available_in(self):
681         """Test GLIB_AVAILABLE_ENUMERATOR_IN_2_68 handling
682         https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
683         h_contents = """
684         typedef enum {
685           G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_AVAILABLE_ENUMERATOR_IN_2_68 = (1<<2)
686         } GDBusServerFlags;
687         """
688         result = self.runMkenumsWithHeader(h_contents)
689         self.assertEqual("", result.err)
690         self.assertSingleEnum(
691             result,
692             "GDBusServerFlags",
693             "g_dbus_server_flags",
694             "G_DBUS_SERVER_FLAGS",
695             "DBUS_SERVER_FLAGS",
696             "G",
697             "",
698             "flags",
699             "Flags",
700             "FLAGS",
701             "G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
702             "user",
703             "4",
704         )
705
706     def test_deprecated_in(self):
707         """Test GLIB_DEPRECATED_ENUMERATOR_IN_2_68 handling
708         https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
709         h_contents = """
710         typedef enum {
711           G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_DEPRECATED_ENUMERATOR_IN_2_68 = (1<<2)
712         } GDBusServerFlags;
713         """
714         result = self.runMkenumsWithHeader(h_contents)
715         self.assertEqual("", result.err)
716         self.assertSingleEnum(
717             result,
718             "GDBusServerFlags",
719             "g_dbus_server_flags",
720             "G_DBUS_SERVER_FLAGS",
721             "DBUS_SERVER_FLAGS",
722             "G",
723             "",
724             "flags",
725             "Flags",
726             "FLAGS",
727             "G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
728             "user",
729             "4",
730         )
731
732     def test_deprecated_in_for(self):
733         """Test GLIB_DEPRECATED_ENUMERATOR_IN_2_68_FOR() handling
734         https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
735         h_contents = """
736         typedef enum {
737           G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_DEPRECATED_ENUMERATOR_IN_2_68_FOR(G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER2) = (1<<2)
738         } GDBusServerFlags;
739         """
740         result = self.runMkenumsWithHeader(h_contents)
741         self.assertEqual("", result.err)
742         self.assertSingleEnum(
743             result,
744             "GDBusServerFlags",
745             "g_dbus_server_flags",
746             "G_DBUS_SERVER_FLAGS",
747             "DBUS_SERVER_FLAGS",
748             "G",
749             "",
750             "flags",
751             "Flags",
752             "FLAGS",
753             "G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
754             "user",
755             "4",
756         )
757
758     def test_enum_symbolic_expression(self):
759         """Test use of symbol in value expression."""
760         h_contents = """
761         typedef enum {
762           /*< private >*/
763           ENUM_VALUE_PRIVATE = 5,
764           /*< public >*/
765           ENUM_VALUE_PUBLIC = ENUM_VALUE_PRIVATE + 2,
766         } TestSymbolicEnum;
767         """
768
769         result = self.runMkenumsWithHeader(h_contents)
770         self.assertEqual("", result.err)
771         self.assertSingleEnum(
772             result,
773             "TestSymbolicEnum",
774             "test_symbolic_enum",
775             "TEST_SYMBOLIC_ENUM",
776             "SYMBOLIC_ENUM",
777             "TEST",
778             "",
779             "enum",
780             "Enum",
781             "ENUM",
782             "ENUM_VALUE_PUBLIC",
783             "public",
784             "7",
785         )
786
787
788 class TestRspMkenums(TestMkenums):
789     """Run all tests again in @rspfile mode"""
790
791     rspfile = True
792
793
794 if __name__ == "__main__":
795     unittest.main(testRunner=taptestrunner.TAPTestRunner())