• 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    # 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 ***/
140file-header
141/*** END file-header ***/
142
143/*** BEGIN file-production ***/
144file-production
145filename: @filename@
146basename: @basename@
147/*** END file-production ***/
148
149/*** BEGIN enumeration-production ***/
150enumeration-production
151EnumName: @EnumName@
152enum_name: @enum_name@
153ENUMNAME: @ENUMNAME@
154ENUMSHORT: @ENUMSHORT@
155ENUMPREFIX: @ENUMPREFIX@
156type: @type@
157Type: @Type@
158TYPE: @TYPE@
159/*** END enumeration-production ***/
160
161/*** BEGIN value-header ***/
162value-header
163EnumName: @EnumName@
164enum_name: @enum_name@
165ENUMNAME: @ENUMNAME@
166ENUMSHORT: @ENUMSHORT@
167ENUMPREFIX: @ENUMPREFIX@
168type: @type@
169Type: @Type@
170TYPE: @TYPE@
171/*** END value-header ***/
172
173/*** BEGIN value-production ***/
174value-production
175VALUENAME: @VALUENAME@
176valuenick: @valuenick@
177valuenum: @valuenum@
178type: @type@
179Type: @Type@
180TYPE: @TYPE@
181/*** END value-production ***/
182
183/*** BEGIN value-tail ***/
184value-tail
185EnumName: @EnumName@
186enum_name: @enum_name@
187ENUMNAME: @ENUMNAME@
188ENUMSHORT: @ENUMSHORT@
189ENUMPREFIX: @ENUMPREFIX@
190type: @type@
191Type: @Type@
192TYPE: @TYPE@
193/*** END value-tail ***/
194
195/*** BEGIN comment ***/
196comment
197comment: @comment@
198/*** END comment ***/
199
200/*** BEGIN file-tail ***/
201file-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('''
247comment
248comment: {standard_top_comment}
249
250
251file-header
252file-production
253filename: {filename}
254basename: {basename}
255enumeration-production
256EnumName: {enum_name_camel}
257enum_name: {enum_name_lower}
258ENUMNAME: {enum_name_upper}
259ENUMSHORT: {enum_name_short}
260ENUMPREFIX: {enum_prefix}
261type: {type_lower}
262Type: {type_camel}
263TYPE: {type_upper}
264value-header
265EnumName: {enum_name_camel}
266enum_name: {enum_name_lower}
267ENUMNAME: {enum_name_upper}
268ENUMSHORT: {enum_name_short}
269ENUMPREFIX: {enum_prefix}
270type: {type_lower}
271Type: {type_camel}
272TYPE: {type_upper}
273value-production
274VALUENAME: {value_name}
275valuenick: {value_nick}
276valuenum: {value_num}
277type: {type_lower}
278Type: {type_camel}
279TYPE: {type_upper}
280value-tail
281EnumName: {enum_name_camel}
282enum_name: {enum_name_lower}
283ENUMNAME: {enum_name_upper}
284ENUMSHORT: {enum_name_short}
285ENUMPREFIX: {enum_prefix}
286type: {type_lower}
287Type: {type_camel}
288TYPE: {type_upper}
289file-tail
290
291comment
292comment: {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('''
325comment
326comment: {standard_top_comment}
327
328
329file-header
330file-tail
331
332comment
333comment: {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('''
341comment
342comment: {standard_top_comment}
343
344
345file-header
346file-tail
347
348comment
349comment: {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 ***/
439file-header
440filename: @filename@
441basename: @basename@
442/*** END file-header ***/
443
444/*** BEGIN comment ***/
445comment
446comment: @comment@
447/*** END comment ***/
448
449/*** BEGIN file-tail ***/
450file-tail
451filename: @filename@
452basename: @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('''
465comment
466comment: {standard_top_comment}
467
468
469file-header
470filename: @filename@
471basename: @basename@
472file-tail
473filename: @filename@
474basename: @basename@
475
476comment
477comment: {standard_bottom_comment}
478'''.format(**result.subs).strip(), result.out)
479
480
481class TestRspMkenums(TestMkenums):
482    '''Run all tests again in @rspfile mode'''
483    rspfile = True
484
485
486if __name__ == '__main__':
487    unittest.main(testRunner=taptestrunner.TAPTestRunner())
488