Imported Upstream version 2.61.2
[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     # Track the cwd, we want to back out to that to clean up our tempdir
51     cwd = ''
52     rspfile = False
53
54     def setUp(self):
55         self.timeout_seconds = 10  # seconds per test
56         self.tmpdir = tempfile.TemporaryDirectory()
57         self.cwd = os.getcwd()
58         os.chdir(self.tmpdir.name)
59         print('tmpdir:', self.tmpdir.name)
60         if 'G_TEST_BUILDDIR' in os.environ:
61             self.__mkenums = \
62                 os.path.join(os.environ['G_TEST_BUILDDIR'], '..',
63                              'glib-mkenums')
64         else:
65             self.__mkenums = shutil.which('glib-mkenums')
66         print('rspfile: {}, mkenums:'.format(self.rspfile), self.__mkenums)
67
68     def tearDown(self):
69         os.chdir(self.cwd)
70         self.tmpdir.cleanup()
71
72     def _write_rspfile(self, argv):
73         import shlex
74         with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, mode='w',
75                                          delete=False) as f:
76             contents = ' '.join([shlex.quote(arg) for arg in argv])
77             print('Response file contains:', contents)
78             f.write(contents)
79             f.flush()
80             return f.name
81
82     def runMkenums(self, *args):
83         if self.rspfile:
84             rspfile = self._write_rspfile(args)
85             args = ['@' + rspfile]
86         argv = [self.__mkenums]
87
88         # shebang lines are not supported on native
89         # Windows consoles
90         if os.name == 'nt':
91             argv.insert(0, sys.executable)
92
93         argv.extend(args)
94         print('Running:', argv)
95
96         env = os.environ.copy()
97         env['LC_ALL'] = 'C.UTF-8'
98         print('Environment:', env)
99
100         # We want to ensure consistent line endings...
101         info = subprocess.run(argv, timeout=self.timeout_seconds,
102                               stdout=subprocess.PIPE,
103                               stderr=subprocess.PIPE,
104                               env=env,
105                               universal_newlines=True)
106         info.check_returncode()
107         out = info.stdout.strip()
108         err = info.stderr.strip()
109
110         # Known substitutions for standard boilerplate
111         subs = {
112             'standard_top_comment':
113                 'This file is generated by glib-mkenums, do not modify '
114                 'it. This code is licensed under the same license as the '
115                 'containing project. Note that it links to GLib, so must '
116                 'comply with the LGPL linking clauses.',
117             'standard_bottom_comment': 'Generated data ends here'
118         }
119
120         result = Result(info, out, err, subs)
121
122         print('Output:', result.out)
123         return result
124
125     def runMkenumsWithTemplate(self, template_contents, *args):
126         with tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
127                                          suffix='.template',
128                                          delete=False) as template_file:
129             # Write out the template.
130             template_file.write(template_contents.encode('utf-8'))
131             print(template_file.name + ':', template_contents)
132             template_file.flush()
133
134             return self.runMkenums('--template', template_file.name, *args)
135
136     def runMkenumsWithAllSubstitutions(self, *args):
137         '''Run glib-mkenums with a template which outputs all substitutions.'''
138         template_contents = '''
139 /*** BEGIN file-header ***/
140 file-header
141 /*** END file-header ***/
142
143 /*** BEGIN file-production ***/
144 file-production
145 filename: @filename@
146 basename: @basename@
147 /*** END file-production ***/
148
149 /*** BEGIN enumeration-production ***/
150 enumeration-production
151 EnumName: @EnumName@
152 enum_name: @enum_name@
153 ENUMNAME: @ENUMNAME@
154 ENUMSHORT: @ENUMSHORT@
155 ENUMPREFIX: @ENUMPREFIX@
156 type: @type@
157 Type: @Type@
158 TYPE: @TYPE@
159 /*** END enumeration-production ***/
160
161 /*** BEGIN value-header ***/
162 value-header
163 EnumName: @EnumName@
164 enum_name: @enum_name@
165 ENUMNAME: @ENUMNAME@
166 ENUMSHORT: @ENUMSHORT@
167 ENUMPREFIX: @ENUMPREFIX@
168 type: @type@
169 Type: @Type@
170 TYPE: @TYPE@
171 /*** END value-header ***/
172
173 /*** BEGIN value-production ***/
174 value-production
175 VALUENAME: @VALUENAME@
176 valuenick: @valuenick@
177 valuenum: @valuenum@
178 type: @type@
179 Type: @Type@
180 TYPE: @TYPE@
181 /*** END value-production ***/
182
183 /*** BEGIN value-tail ***/
184 value-tail
185 EnumName: @EnumName@
186 enum_name: @enum_name@
187 ENUMNAME: @ENUMNAME@
188 ENUMSHORT: @ENUMSHORT@
189 ENUMPREFIX: @ENUMPREFIX@
190 type: @type@
191 Type: @Type@
192 TYPE: @TYPE@
193 /*** END value-tail ***/
194
195 /*** BEGIN comment ***/
196 comment
197 comment: @comment@
198 /*** END comment ***/
199
200 /*** BEGIN file-tail ***/
201 file-tail
202 /*** END file-tail ***/
203 '''
204         return self.runMkenumsWithTemplate(template_contents, *args)
205
206     def runMkenumsWithHeader(self, h_contents, encoding='utf-8'):
207         with tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
208                                          suffix='.h',
209                                          delete=False) as h_file:
210             # Write out the header to be scanned.
211             h_file.write(h_contents.encode(encoding))
212             print(h_file.name + ':', h_contents)
213             h_file.flush()
214
215             # Run glib-mkenums with a template which outputs all substitutions.
216             result = self.runMkenumsWithAllSubstitutions(h_file.name)
217
218             # Known substitutions for generated filenames.
219             result.subs.update({
220                 'filename': h_file.name,
221                 'basename': os.path.basename(h_file.name),
222             })
223
224             return result
225
226     def assertSingleEnum(self, result, enum_name_camel, enum_name_lower,
227                          enum_name_upper, enum_name_short, enum_prefix,
228                          type_lower, type_camel, type_upper,
229                          value_name, value_nick, value_num):
230         """Assert that out (from runMkenumsWithHeader()) contains a single
231            enum and value matching the given arguments."""
232         subs = dict({
233             'enum_name_camel': enum_name_camel,
234             'enum_name_lower': enum_name_lower,
235             'enum_name_upper': enum_name_upper,
236             'enum_name_short': enum_name_short,
237             'enum_prefix': enum_prefix,
238             'type_lower': type_lower,
239             'type_camel': type_camel,
240             'type_upper': type_upper,
241             'value_name': value_name,
242             'value_nick': value_nick,
243             'value_num': value_num,
244         }, **result.subs)
245
246         self.assertEqual('''
247 comment
248 comment: {standard_top_comment}
249
250
251 file-header
252 file-production
253 filename: {filename}
254 basename: {basename}
255 enumeration-production
256 EnumName: {enum_name_camel}
257 enum_name: {enum_name_lower}
258 ENUMNAME: {enum_name_upper}
259 ENUMSHORT: {enum_name_short}
260 ENUMPREFIX: {enum_prefix}
261 type: {type_lower}
262 Type: {type_camel}
263 TYPE: {type_upper}
264 value-header
265 EnumName: {enum_name_camel}
266 enum_name: {enum_name_lower}
267 ENUMNAME: {enum_name_upper}
268 ENUMSHORT: {enum_name_short}
269 ENUMPREFIX: {enum_prefix}
270 type: {type_lower}
271 Type: {type_camel}
272 TYPE: {type_upper}
273 value-production
274 VALUENAME: {value_name}
275 valuenick: {value_nick}
276 valuenum: {value_num}
277 type: {type_lower}
278 Type: {type_camel}
279 TYPE: {type_upper}
280 value-tail
281 EnumName: {enum_name_camel}
282 enum_name: {enum_name_lower}
283 ENUMNAME: {enum_name_upper}
284 ENUMSHORT: {enum_name_short}
285 ENUMPREFIX: {enum_prefix}
286 type: {type_lower}
287 Type: {type_camel}
288 TYPE: {type_upper}
289 file-tail
290
291 comment
292 comment: {standard_bottom_comment}
293 '''.format(**subs).strip(), result.out)
294
295     def test_help(self):
296         """Test the --help argument."""
297         result = self.runMkenums('--help')
298         self.assertIn('usage: glib-mkenums', result.out)
299
300     def test_no_args(self):
301         """Test running with no arguments at all."""
302         result = self.runMkenums()
303         self.assertEqual('', result.err)
304         self.assertEqual('''/* {standard_top_comment} */
305
306
307 /* {standard_bottom_comment} */'''.format(**result.subs),
308                           result.out.strip())
309
310     def test_empty_template(self):
311         """Test running with an empty template and no header files."""
312         result = self.runMkenumsWithTemplate('')
313         self.assertEqual('', result.err)
314         self.assertEqual('''/* {standard_top_comment} */
315
316
317 /* {standard_bottom_comment} */'''.format(**result.subs),
318                           result.out.strip())
319
320     def test_no_headers(self):
321         """Test running with a complete template, but no header files."""
322         result = self.runMkenumsWithAllSubstitutions()
323         self.assertEqual('', result.err)
324         self.assertEqual('''
325 comment
326 comment: {standard_top_comment}
327
328
329 file-header
330 file-tail
331
332 comment
333 comment: {standard_bottom_comment}
334 '''.format(**result.subs).strip(), result.out)
335
336     def test_empty_header(self):
337         """Test an empty header."""
338         result = self.runMkenumsWithHeader('')
339         self.assertEqual('', result.err)
340         self.assertEqual('''
341 comment
342 comment: {standard_top_comment}
343
344
345 file-header
346 file-tail
347
348 comment
349 comment: {standard_bottom_comment}
350 '''.format(**result.subs).strip(), result.out)
351
352     def test_enum_name(self):
353         """Test typedefs with an enum and a typedef name. Bug #794506."""
354         h_contents = '''
355         typedef enum _SomeEnumIdentifier {
356           ENUM_VALUE
357         } SomeEnumIdentifier;
358         '''
359         result = self.runMkenumsWithHeader(h_contents)
360         self.assertEqual('', result.err)
361         self.assertSingleEnum(result, 'SomeEnumIdentifier',
362                               'some_enum_identifier', 'SOME_ENUM_IDENTIFIER',
363                               'ENUM_IDENTIFIER', 'SOME', 'enum', 'Enum',
364                               'ENUM', 'ENUM_VALUE', 'value', '0')
365
366     def test_non_utf8_encoding(self):
367         """Test source files with non-UTF-8 encoding. Bug #785113."""
368         h_contents = '''
369         /* Copyright © La Peña */
370         typedef enum {
371           ENUM_VALUE
372         } SomeEnumIdentifier;
373         '''
374         result = self.runMkenumsWithHeader(h_contents, encoding='iso-8859-1')
375         self.assertIn('WARNING: UnicodeWarning: ', result.err)
376         self.assertSingleEnum(result, 'SomeEnumIdentifier',
377                               'some_enum_identifier', 'SOME_ENUM_IDENTIFIER',
378                               'ENUM_IDENTIFIER', 'SOME', 'enum', 'Enum',
379                               'ENUM', 'ENUM_VALUE', 'value', '0')
380
381     def test_reproducible(self):
382         """Test builds are reproducible regardless of file ordering.
383         Bug #691436."""
384         template_contents = 'template'
385
386         h_contents1 = '''
387         typedef enum {
388           FIRST,
389         } Header1;
390         '''
391
392         h_contents2 = '''
393         typedef enum {
394           SECOND,
395         } Header2;
396         '''
397
398         with tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
399                                          suffix='1.h', delete=False) as h_file1, \
400                 tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
401                                             suffix='2.h', delete=False) as h_file2:
402             # Write out the headers.
403             h_file1.write(h_contents1.encode('utf-8'))
404             h_file2.write(h_contents2.encode('utf-8'))
405
406             h_file1.flush()
407             h_file2.flush()
408
409             # Run glib-mkenums with the headers in one order, and then again
410             # in another order.
411             result1 = self.runMkenumsWithTemplate(template_contents,
412                                                   h_file1.name, h_file2.name)
413             self.assertEqual('', result1.err)
414
415             result2 = self.runMkenumsWithTemplate(template_contents,
416                                                   h_file2.name, h_file1.name)
417             self.assertEqual('', result2.err)
418
419             # The output should be the same.
420             self.assertEqual(result1.out, result2.out)
421
422     def test_no_nick(self):
423         """Test trigraphs with a desc but no nick. Issue #1360."""
424         h_contents = '''
425         typedef enum {
426           GEGL_SAMPLER_NEAREST = 0,   /*< desc="nearest"      >*/
427         } GeglSamplerType;
428         '''
429         result = self.runMkenumsWithHeader(h_contents)
430         self.assertEqual('', result.err)
431         self.assertSingleEnum(result, 'GeglSamplerType',
432                               'gegl_sampler_type', 'GEGL_SAMPLER_TYPE',
433                               'SAMPLER_TYPE', 'GEGL', 'enum', 'Enum',
434                               'ENUM', 'GEGL_SAMPLER_NEAREST', 'nearest', '0')
435
436     def test_filename_basename_in_fhead_ftail(self):
437         template_contents = '''
438 /*** BEGIN file-header ***/
439 file-header
440 filename: @filename@
441 basename: @basename@
442 /*** END file-header ***/
443
444 /*** BEGIN comment ***/
445 comment
446 comment: @comment@
447 /*** END comment ***/
448
449 /*** BEGIN file-tail ***/
450 file-tail
451 filename: @filename@
452 basename: @basename@
453 /*** END file-tail ***/'''
454         result = self.runMkenumsWithTemplate(template_contents)
455         self.assertEqual(
456             textwrap.dedent(
457                 '''
458                 WARNING: @filename@ used in file-header section.
459                 WARNING: @basename@ used in file-header section.
460                 WARNING: @filename@ used in file-tail section.
461                 WARNING: @basename@ used in file-tail section.
462                 ''').strip(),
463             result.err)
464         self.assertEqual('''
465 comment
466 comment: {standard_top_comment}
467
468
469 file-header
470 filename: @filename@
471 basename: @basename@
472 file-tail
473 filename: @filename@
474 basename: @basename@
475
476 comment
477 comment: {standard_bottom_comment}
478 '''.format(**result.subs).strip(), result.out)
479
480
481 class TestRspMkenums(TestMkenums):
482     '''Run all tests again in @rspfile mode'''
483     rspfile = True
484
485
486 if __name__ == '__main__':
487     unittest.main(testRunner=taptestrunner.TAPTestRunner())