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