| #!/usr/bin/python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright © 2018 Endless Mobile, Inc. |
| # |
| # This library is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2.1 of the License, or (at your option) any later version. |
| # |
| # This library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with this library; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| # MA 02110-1301 USA |
| |
| """Integration tests for glib-mkenums utility.""" |
| |
| import collections |
| import os |
| import subprocess |
| import tempfile |
| import unittest |
| |
| import taptestrunner |
| |
| |
| Result = collections.namedtuple('Result', ('info', 'out', 'err', 'subs')) |
| |
| |
| class TestMkenums(unittest.TestCase): |
| """Integration test for running glib-mkenums. |
| |
| This can be run when installed or uninstalled. When uninstalled, it |
| requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set. |
| |
| The idea with this test harness is to test the glib-mkenums utility, its |
| handling of command line arguments, its exit statuses, and its handling of |
| various C source codes. In future we could split the core glib-mkenums |
| parsing and generation code out into a library and unit test that, and |
| convert this test to just check command line behaviour. |
| """ |
| |
| def setUp(self): |
| self.timeout_seconds = 10 # seconds per test |
| self.tmpdir = tempfile.TemporaryDirectory() |
| os.chdir(self.tmpdir.name) |
| print('tmpdir:', self.tmpdir.name) |
| if 'G_TEST_BUILDDIR' in os.environ: |
| self.__mkenums = \ |
| os.path.join(os.environ['G_TEST_BUILDDIR'], '..', |
| 'glib-mkenums') |
| else: |
| self.__mkenums = os.path.join('/', 'usr', 'bin', 'glib-mkenums') |
| print('mkenums:', self.__mkenums) |
| |
| def tearDown(self): |
| self.tmpdir.cleanup() |
| |
| def runMkenums(self, *args): |
| argv = [self.__mkenums] |
| argv.extend(args) |
| print('Running:', argv) |
| |
| env = os.environ.copy() |
| env['LC_ALL'] = 'C.UTF-8' |
| print('Environment:', env) |
| |
| info = subprocess.run(argv, timeout=self.timeout_seconds, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| env=env) |
| info.check_returncode() |
| out = info.stdout.decode('utf-8').strip() |
| err = info.stderr.decode('utf-8').strip() |
| |
| # Known substitutions for standard boilerplate |
| subs = { |
| 'standard_top_comment': |
| 'This file is generated by glib-mkenums, do not modify ' |
| 'it. This code is licensed under the same license as the ' |
| 'containing project. Note that it links to GLib, so must ' |
| 'comply with the LGPL linking clauses.', |
| 'standard_bottom_comment': 'Generated data ends here' |
| } |
| |
| result = Result(info, out, err, subs) |
| |
| print('Output:', result.out) |
| return result |
| |
| def runMkenumsWithTemplate(self, template_contents, *args): |
| with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, |
| suffix='.template') as template_file: |
| # Write out the template. |
| template_file.write(template_contents.encode('utf-8')) |
| print(template_file.name + ':', template_contents) |
| template_file.flush() |
| |
| return self.runMkenums('--template', template_file.name, *args) |
| |
| def runMkenumsWithAllSubstitutions(self, *args): |
| '''Run glib-mkenums with a template which outputs all substitutions.''' |
| template_contents = ''' |
| /*** BEGIN file-header ***/ |
| file-header |
| filename: @filename@ |
| basename: @basename@ |
| /*** END file-header ***/ |
| |
| /*** BEGIN file-production ***/ |
| file-production |
| filename: @filename@ |
| basename: @basename@ |
| /*** END file-production ***/ |
| |
| /*** BEGIN enumeration-production ***/ |
| enumeration-production |
| EnumName: @EnumName@ |
| enum_name: @enum_name@ |
| ENUMNAME: @ENUMNAME@ |
| ENUMSHORT: @ENUMSHORT@ |
| ENUMPREFIX: @ENUMPREFIX@ |
| type: @type@ |
| Type: @Type@ |
| TYPE: @TYPE@ |
| /*** END enumeration-production ***/ |
| |
| /*** BEGIN value-header ***/ |
| value-header |
| EnumName: @EnumName@ |
| enum_name: @enum_name@ |
| ENUMNAME: @ENUMNAME@ |
| ENUMSHORT: @ENUMSHORT@ |
| ENUMPREFIX: @ENUMPREFIX@ |
| type: @type@ |
| Type: @Type@ |
| TYPE: @TYPE@ |
| /*** END value-header ***/ |
| |
| /*** BEGIN value-production ***/ |
| value-production |
| VALUENAME: @VALUENAME@ |
| valuenick: @valuenick@ |
| valuenum: @valuenum@ |
| type: @type@ |
| Type: @Type@ |
| TYPE: @TYPE@ |
| /*** END value-production ***/ |
| |
| /*** BEGIN value-tail ***/ |
| value-tail |
| EnumName: @EnumName@ |
| enum_name: @enum_name@ |
| ENUMNAME: @ENUMNAME@ |
| ENUMSHORT: @ENUMSHORT@ |
| ENUMPREFIX: @ENUMPREFIX@ |
| type: @type@ |
| Type: @Type@ |
| TYPE: @TYPE@ |
| /*** END value-tail ***/ |
| |
| /*** BEGIN comment ***/ |
| comment |
| comment: @comment@ |
| /*** END comment ***/ |
| |
| /*** BEGIN file-tail ***/ |
| file-tail |
| filename: @filename@ |
| basename: @basename@ |
| /*** END file-tail ***/ |
| ''' |
| return self.runMkenumsWithTemplate(template_contents, *args) |
| |
| def runMkenumsWithHeader(self, h_contents, encoding='utf-8'): |
| with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, |
| suffix='.h') as h_file: |
| # Write out the header to be scanned. |
| h_file.write(h_contents.encode(encoding)) |
| print(h_file.name + ':', h_contents) |
| h_file.flush() |
| |
| # Run glib-mkenums with a template which outputs all substitutions. |
| result = self.runMkenumsWithAllSubstitutions(h_file.name) |
| |
| # Known substitutions for generated filenames. |
| result.subs.update({ |
| 'filename': h_file.name, |
| 'basename': os.path.basename(h_file.name), |
| }) |
| |
| return result |
| |
| def assertSingleEnum(self, result, enum_name_camel, enum_name_lower, |
| enum_name_upper, enum_name_short, enum_prefix, |
| type_lower, type_camel, type_upper, |
| value_name, value_nick, value_num): |
| """Assert that out (from runMkenumsWithHeader()) contains a single |
| enum and value matching the given arguments.""" |
| subs = dict({ |
| 'enum_name_camel': enum_name_camel, |
| 'enum_name_lower': enum_name_lower, |
| 'enum_name_upper': enum_name_upper, |
| 'enum_name_short': enum_name_short, |
| 'enum_prefix': enum_prefix, |
| 'type_lower': type_lower, |
| 'type_camel': type_camel, |
| 'type_upper': type_upper, |
| 'value_name': value_name, |
| 'value_nick': value_nick, |
| 'value_num': value_num, |
| }, **result.subs) |
| |
| self.assertEqual(''' |
| comment |
| comment: {standard_top_comment} |
| |
| |
| file-header |
| filename: {filename} |
| basename: {basename} |
| file-production |
| filename: {filename} |
| basename: {basename} |
| enumeration-production |
| EnumName: {enum_name_camel} |
| enum_name: {enum_name_lower} |
| ENUMNAME: {enum_name_upper} |
| ENUMSHORT: {enum_name_short} |
| ENUMPREFIX: {enum_prefix} |
| type: {type_lower} |
| Type: {type_camel} |
| TYPE: {type_upper} |
| value-header |
| EnumName: {enum_name_camel} |
| enum_name: {enum_name_lower} |
| ENUMNAME: {enum_name_upper} |
| ENUMSHORT: {enum_name_short} |
| ENUMPREFIX: {enum_prefix} |
| type: {type_lower} |
| Type: {type_camel} |
| TYPE: {type_upper} |
| value-production |
| VALUENAME: {value_name} |
| valuenick: {value_nick} |
| valuenum: {value_num} |
| type: {type_lower} |
| Type: {type_camel} |
| TYPE: {type_upper} |
| value-tail |
| EnumName: {enum_name_camel} |
| enum_name: {enum_name_lower} |
| ENUMNAME: {enum_name_upper} |
| ENUMSHORT: {enum_name_short} |
| ENUMPREFIX: {enum_prefix} |
| type: {type_lower} |
| Type: {type_camel} |
| TYPE: {type_upper} |
| file-tail |
| filename: ARGV |
| basename: {basename} |
| |
| comment |
| comment: {standard_bottom_comment} |
| '''.format(**subs).strip(), result.out) |
| |
| def test_help(self): |
| """Test the --help argument.""" |
| result = self.runMkenums('--help') |
| self.assertIn('usage: glib-mkenums', result.out) |
| |
| def test_empty_header(self): |
| """Test an empty header.""" |
| result = self.runMkenumsWithHeader('') |
| self.assertEqual('', result.err) |
| self.assertEqual(''' |
| comment |
| comment: {standard_top_comment} |
| |
| |
| file-header |
| filename: {filename} |
| basename: {basename} |
| file-tail |
| filename: ARGV |
| basename: {basename} |
| |
| comment |
| comment: {standard_bottom_comment} |
| '''.format(**result.subs).strip(), result.out) |
| |
| def test_enum_name(self): |
| """Test typedefs with an enum and a typedef name. Bug #794506.""" |
| h_contents = ''' |
| typedef enum _SomeEnumIdentifier { |
| ENUM_VALUE |
| } SomeEnumIdentifier; |
| ''' |
| result = self.runMkenumsWithHeader(h_contents) |
| self.assertEqual('', result.err) |
| self.assertSingleEnum(result, 'SomeEnumIdentifier', |
| 'some_enum_identifier', 'SOME_ENUM_IDENTIFIER', |
| 'ENUM_IDENTIFIER', 'SOME', 'enum', 'Enum', |
| 'ENUM', 'ENUM_VALUE', 'value', '0') |
| |
| def test_non_utf8_encoding(self): |
| """Test source files with non-UTF-8 encoding. Bug #785113.""" |
| h_contents = ''' |
| /* Copyright © La Peña */ |
| typedef enum { |
| ENUM_VALUE |
| } SomeEnumIdentifier; |
| ''' |
| result = self.runMkenumsWithHeader(h_contents, encoding='iso-8859-1') |
| self.assertIn('WARNING: UnicodeWarning: ', result.err) |
| self.assertSingleEnum(result, 'SomeEnumIdentifier', |
| 'some_enum_identifier', 'SOME_ENUM_IDENTIFIER', |
| 'ENUM_IDENTIFIER', 'SOME', 'enum', 'Enum', |
| 'ENUM', 'ENUM_VALUE', 'value', '0') |
| |
| def test_reproducible(self): |
| """Test builds are reproducible regardless of file ordering. |
| Bug #691436.""" |
| template_contents = 'template' |
| |
| h_contents1 = ''' |
| typedef enum { |
| FIRST, |
| } Header1; |
| ''' |
| |
| h_contents2 = ''' |
| typedef enum { |
| SECOND, |
| } Header2; |
| ''' |
| |
| with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, |
| suffix='1.h') as h_file1, \ |
| tempfile.NamedTemporaryFile(dir=self.tmpdir.name, |
| suffix='2.h') as h_file2: |
| # Write out the headers. |
| h_file1.write(h_contents1.encode('utf-8')) |
| h_file2.write(h_contents2.encode('utf-8')) |
| |
| h_file1.flush() |
| h_file2.flush() |
| |
| # Run glib-mkenums with the headers in one order, and then again |
| # in another order. |
| result1 = self.runMkenumsWithTemplate(template_contents, |
| h_file1.name, h_file2.name) |
| self.assertEqual('', result1.err) |
| |
| result2 = self.runMkenumsWithTemplate(template_contents, |
| h_file2.name, h_file1.name) |
| self.assertEqual('', result2.err) |
| |
| # The output should be the same. |
| self.assertEqual(result1.out, result2.out) |
| |
| def test_no_nick(self): |
| """Test trigraphs with a desc but no nick. Issue #1360.""" |
| h_contents = ''' |
| typedef enum { |
| GEGL_SAMPLER_NEAREST = 0, /*< desc="nearest" >*/ |
| } GeglSamplerType; |
| ''' |
| result = self.runMkenumsWithHeader(h_contents) |
| self.assertEqual('', result.err) |
| self.assertSingleEnum(result, 'GeglSamplerType', |
| 'gegl_sampler_type', 'GEGL_SAMPLER_TYPE', |
| 'SAMPLER_TYPE', 'GEGL', 'enum', 'Enum', |
| 'ENUM', 'GEGL_SAMPLER_NEAREST', 'nearest', '0') |
| |
| |
| if __name__ == '__main__': |
| unittest.main(testRunner=taptestrunner.TAPTestRunner()) |