2 # -*- coding: utf-8 -*-
4 # Copyright © 2018 Endless Mobile, Inc.
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.
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.
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,
21 """Integration tests for glib-mkenums utility."""
35 Result = collections.namedtuple('Result', ('info', 'out', 'err', 'subs'))
38 class TestMkenums(unittest.TestCase):
39 """Integration test for running glib-mkenums.
41 This can be run when installed or uninstalled. When uninstalled, it
42 requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set.
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.
50 # Track the cwd, we want to back out to that to clean up our tempdir
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:
62 os.path.join(os.environ['G_TEST_BUILDDIR'], '..',
65 self.__mkenums = shutil.which('glib-mkenums')
66 print('rspfile: {}, mkenums:'.format(self.rspfile), self.__mkenums)
72 def _write_rspfile(self, argv):
74 with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, mode='w',
76 contents = ' '.join([shlex.quote(arg) for arg in argv])
77 print('Response file contains:', contents)
82 def runMkenums(self, *args):
84 rspfile = self._write_rspfile(args)
85 args = ['@' + rspfile]
86 argv = [self.__mkenums]
88 # shebang lines are not supported on native
91 argv.insert(0, sys.executable)
94 print('Running:', argv)
96 env = os.environ.copy()
97 env['LC_ALL'] = 'C.UTF-8'
98 print('Environment:', env)
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,
105 universal_newlines=True)
106 info.check_returncode()
107 out = info.stdout.strip()
108 err = info.stderr.strip()
110 # Known substitutions for standard boilerplate
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'
120 result = Result(info, out, err, subs)
122 print('Output:', result.out)
125 def runMkenumsWithTemplate(self, template_contents, *args):
126 with tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
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()
134 return self.runMkenums('--template', template_file.name, *args)
136 def runMkenumsWithAllSubstitutions(self, *args):
137 '''Run glib-mkenums with a template which outputs all substitutions.'''
138 template_contents = '''
139 /*** BEGIN file-header ***/
141 /*** END file-header ***/
143 /*** BEGIN file-production ***/
147 /*** END file-production ***/
149 /*** BEGIN enumeration-production ***/
150 enumeration-production
152 enum_name: @enum_name@
154 ENUMSHORT: @ENUMSHORT@
155 ENUMPREFIX: @ENUMPREFIX@
159 /*** END enumeration-production ***/
161 /*** BEGIN value-header ***/
164 enum_name: @enum_name@
166 ENUMSHORT: @ENUMSHORT@
167 ENUMPREFIX: @ENUMPREFIX@
171 /*** END value-header ***/
173 /*** BEGIN value-production ***/
175 VALUENAME: @VALUENAME@
176 valuenick: @valuenick@
181 /*** END value-production ***/
183 /*** BEGIN value-tail ***/
186 enum_name: @enum_name@
188 ENUMSHORT: @ENUMSHORT@
189 ENUMPREFIX: @ENUMPREFIX@
193 /*** END value-tail ***/
195 /*** BEGIN comment ***/
198 /*** END comment ***/
200 /*** BEGIN file-tail ***/
202 /*** END file-tail ***/
204 return self.runMkenumsWithTemplate(template_contents, *args)
206 def runMkenumsWithHeader(self, h_contents, encoding='utf-8'):
207 with tempfile.NamedTemporaryFile(dir=self.tmpdir.name,
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)
215 # Run glib-mkenums with a template which outputs all substitutions.
216 result = self.runMkenumsWithAllSubstitutions(h_file.name)
218 # Known substitutions for generated filenames.
220 'filename': h_file.name,
221 'basename': os.path.basename(h_file.name),
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."""
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,
248 comment: {standard_top_comment}
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}
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}
274 VALUENAME: {value_name}
275 valuenick: {value_nick}
276 valuenum: {value_num}
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}
292 comment: {standard_bottom_comment}
293 '''.format(**subs).strip(), result.out)
296 """Test the --help argument."""
297 result = self.runMkenums('--help')
298 self.assertIn('usage: glib-mkenums', result.out)
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} */
307 /* {standard_bottom_comment} */'''.format(**result.subs),
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} */
317 /* {standard_bottom_comment} */'''.format(**result.subs),
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)
326 comment: {standard_top_comment}
333 comment: {standard_bottom_comment}
334 '''.format(**result.subs).strip(), result.out)
336 def test_empty_header(self):
337 """Test an empty header."""
338 result = self.runMkenumsWithHeader('')
339 self.assertEqual('', result.err)
342 comment: {standard_top_comment}
349 comment: {standard_bottom_comment}
350 '''.format(**result.subs).strip(), result.out)
352 def test_enum_name(self):
353 """Test typedefs with an enum and a typedef name. Bug #794506."""
355 typedef enum _SomeEnumIdentifier {
357 } SomeEnumIdentifier;
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')
366 def test_non_utf8_encoding(self):
367 """Test source files with non-UTF-8 encoding. Bug #785113."""
369 /* Copyright © La Peña */
372 } SomeEnumIdentifier;
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')
381 def test_reproducible(self):
382 """Test builds are reproducible regardless of file ordering.
384 template_contents = 'template'
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'))
409 # Run glib-mkenums with the headers in one order, and then again
411 result1 = self.runMkenumsWithTemplate(template_contents,
412 h_file1.name, h_file2.name)
413 self.assertEqual('', result1.err)
415 result2 = self.runMkenumsWithTemplate(template_contents,
416 h_file2.name, h_file1.name)
417 self.assertEqual('', result2.err)
419 # The output should be the same.
420 self.assertEqual(result1.out, result2.out)
422 def test_no_nick(self):
423 """Test trigraphs with a desc but no nick. Issue #1360."""
426 GEGL_SAMPLER_NEAREST = 0, /*< desc="nearest" >*/
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')
436 def test_filename_basename_in_fhead_ftail(self):
437 template_contents = '''
438 /*** BEGIN file-header ***/
442 /*** END file-header ***/
444 /*** BEGIN comment ***/
447 /*** END comment ***/
449 /*** BEGIN file-tail ***/
453 /*** END file-tail ***/'''
454 result = self.runMkenumsWithTemplate(template_contents)
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.
466 comment: {standard_top_comment}
477 comment: {standard_bottom_comment}
478 '''.format(**result.subs).strip(), result.out)
481 class TestRspMkenums(TestMkenums):
482 '''Run all tests again in @rspfile mode'''
486 if __name__ == '__main__':
487 unittest.main(testRunner=taptestrunner.TAPTestRunner())