Imported Upstream version 2.67.1
[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 # 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
23 import collections
24 import os
25 import shutil
26 import subprocess
27 import sys
28 import tempfile
29 import textwrap
30 import unittest
31
32 import taptestrunner
33
34
35 Result = collections.namedtuple("Result", ("info", "out", "err", "subs"))
36
37
38 class 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 ***/
145 file-header
146 /*** END file-header ***/
147
148 /*** BEGIN file-production ***/
149 file-production
150 filename: @filename@
151 basename: @basename@
152 /*** END file-production ***/
153
154 /*** BEGIN enumeration-production ***/
155 enumeration-production
156 EnumName: @EnumName@
157 enum_name: @enum_name@
158 ENUMNAME: @ENUMNAME@
159 ENUMSHORT: @ENUMSHORT@
160 ENUMPREFIX: @ENUMPREFIX@
161 enumsince: @enumsince@
162 type: @type@
163 Type: @Type@
164 TYPE: @TYPE@
165 /*** END enumeration-production ***/
166
167 /*** BEGIN value-header ***/
168 value-header
169 EnumName: @EnumName@
170 enum_name: @enum_name@
171 ENUMNAME: @ENUMNAME@
172 ENUMSHORT: @ENUMSHORT@
173 ENUMPREFIX: @ENUMPREFIX@
174 enumsince: @enumsince@
175 type: @type@
176 Type: @Type@
177 TYPE: @TYPE@
178 /*** END value-header ***/
179
180 /*** BEGIN value-production ***/
181 value-production
182 VALUENAME: @VALUENAME@
183 valuenick: @valuenick@
184 valuenum: @valuenum@
185 type: @type@
186 Type: @Type@
187 TYPE: @TYPE@
188 /*** END value-production ***/
189
190 /*** BEGIN value-tail ***/
191 value-tail
192 EnumName: @EnumName@
193 enum_name: @enum_name@
194 ENUMNAME: @ENUMNAME@
195 ENUMSHORT: @ENUMSHORT@
196 ENUMPREFIX: @ENUMPREFIX@
197 enumsince: @enumsince@
198 type: @type@
199 Type: @Type@
200 TYPE: @TYPE@
201 /*** END value-tail ***/
202
203 /*** BEGIN comment ***/
204 comment
205 comment: @comment@
206 /*** END comment ***/
207
208 /*** BEGIN file-tail ***/
209 file-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             """
271 comment
272 comment: {standard_top_comment}
273
274
275 file-header
276 file-production
277 filename: {filename}
278 basename: {basename}
279 enumeration-production
280 EnumName: {enum_name_camel}
281 enum_name: {enum_name_lower}
282 ENUMNAME: {enum_name_upper}
283 ENUMSHORT: {enum_name_short}
284 ENUMPREFIX: {enum_prefix}
285 enumsince: {enum_since}
286 type: {type_lower}
287 Type: {type_camel}
288 TYPE: {type_upper}
289 value-header
290 EnumName: {enum_name_camel}
291 enum_name: {enum_name_lower}
292 ENUMNAME: {enum_name_upper}
293 ENUMSHORT: {enum_name_short}
294 ENUMPREFIX: {enum_prefix}
295 enumsince: {enum_since}
296 type: {type_lower}
297 Type: {type_camel}
298 TYPE: {type_upper}
299 value-production
300 VALUENAME: {value_name}
301 valuenick: {value_nick}
302 valuenum: {value_num}
303 type: {type_lower}
304 Type: {type_camel}
305 TYPE: {type_upper}
306 value-tail
307 EnumName: {enum_name_camel}
308 enum_name: {enum_name_lower}
309 ENUMNAME: {enum_name_upper}
310 ENUMSHORT: {enum_name_short}
311 ENUMPREFIX: {enum_prefix}
312 enumsince: {enum_since}
313 type: {type_lower}
314 Type: {type_camel}
315 TYPE: {type_upper}
316 file-tail
317
318 comment
319 comment: {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             """
365 comment
366 comment: {standard_top_comment}
367
368
369 file-header
370 file-tail
371
372 comment
373 comment: {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             """
386 comment
387 comment: {standard_top_comment}
388
389
390 file-header
391 file-tail
392
393 comment
394 comment: {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 ***/
524 file-header
525 filename: @filename@
526 basename: @basename@
527 /*** END file-header ***/
528
529 /*** BEGIN comment ***/
530 comment
531 comment: @comment@
532 /*** END comment ***/
533
534 /*** BEGIN file-tail ***/
535 file-tail
536 filename: @filename@
537 basename: @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             """
553 comment
554 comment: {standard_top_comment}
555
556
557 file-header
558 filename: @filename@
559 basename: @basename@
560 file-tail
561 filename: @filename@
562 basename: @basename@
563
564 comment
565 comment: {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
599 class TestRspMkenums(TestMkenums):
600     """Run all tests again in @rspfile mode"""
601
602     rspfile = True
603
604
605 if __name__ == "__main__":
606     unittest.main(testRunner=taptestrunner.TAPTestRunner())