diff options
author | Philip Withnall <withnall@endlessm.com> | 2018-05-02 11:05:20 +0100 |
---|---|---|
committer | Philip Withnall <withnall@endlessm.com> | 2018-06-25 13:39:13 +0100 |
commit | e8d755eb8ecc70a8cae7e4de10684e77c21aaad5 (patch) | |
tree | d0feec50674242cb4ce305d660d3167e018ca2a3 /gobject | |
parent | c0e6fa4f19d2d048c7edb86388bcaed74ba64994 (diff) | |
download | glib-e8d755eb8ecc70a8cae7e4de10684e77c21aaad5.tar.gz |
tests: Add initial test framework for glib-mkenums
This allows running glib-mkenums with different C headers and checking
its output.
Signed-off-by: Philip Withnall <withnall@endlessm.com>
https://gitlab.gnome.org/GNOME/glib/issues/1360
Diffstat (limited to 'gobject')
-rw-r--r-- | gobject/tests/meson.build | 11 | ||||
-rw-r--r-- | gobject/tests/mkenums.py | 364 | ||||
-rw-r--r-- | gobject/tests/taptestrunner.py | 176 |
3 files changed, 551 insertions, 0 deletions
diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build index ebec3e84a..252b5ecf8 100644 --- a/gobject/tests/meson.build +++ b/gobject/tests/meson.build @@ -1,3 +1,6 @@ +py3_mod = import('python3') +py3 = py3_mod.find_python() + gobject_tests = [ 'qdata', 'boxed', @@ -78,3 +81,11 @@ exe = executable('signals', dependencies : deps, ) test('signals', exe, env : test_env, suite : ['gobject']) + +test( + 'mkenums.py', + py3, + args: files('mkenums.py'), + env: test_env, + suite: ['gobject'], +)
\ No newline at end of file diff --git a/gobject/tests/mkenums.py b/gobject/tests/mkenums.py new file mode 100644 index 000000000..a8820ae53 --- /dev/null +++ b/gobject/tests/mkenums.py @@ -0,0 +1,364 @@ +#!/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 os +import subprocess +import tempfile +import unittest + +import taptestrunner + + +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) + print('Output:', info.stdout.decode('utf-8')) + return info + + def runMkenumsWithHeader(self, h_contents, encoding='utf-8', *args): + 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 ***/ +''' + + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='.template') as template_file, \ + tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='.h') as h_file: + # Write out the template. + template_file.write(template_contents.encode('utf-8')) + print(template_file.name + ':', template_contents) + + # Write out the header to be scanned. + h_file.write(h_contents.encode(encoding)) + print(h_file.name + ':', h_contents) + + template_file.flush() + h_file.flush() + + # Run glib-mkenums with a template which outputs all substitutions. + info = self.runMkenums('--template', template_file.name, + h_file.name) + info.check_returncode() + out = info.stdout.decode('utf-8').strip() + err = info.stderr.decode('utf-8').strip() + + # Known substitutions for generated filenames. + subs = { + 'filename': h_file.name, + 'basename': os.path.basename(h_file.name), + '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' + } + + return (info, out, err, subs) + + def assertSingleEnum(self, out, subs, 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, + }, **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(), out) + + def test_help(self): + """Test the --help argument.""" + info = self.runMkenums('--help') + info.check_returncode() + + out = info.stdout.decode('utf-8').strip() + self.assertIn('usage: glib-mkenums', out) + + def test_empty_header(self): + """Test an empty header.""" + (info, out, err, subs) = self.runMkenumsWithHeader('') + self.assertEqual('', 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(**subs).strip(), 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; + ''' + (info, out, err, subs) = self.runMkenumsWithHeader(h_contents) + self.assertEqual('', err) + self.assertSingleEnum(out, subs, '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; + ''' + (info, out, err, subs) = \ + self.runMkenumsWithHeader(h_contents, encoding='iso-8859-1') + self.assertIn('WARNING: UnicodeWarning: ', err) + self.assertSingleEnum(out, subs, '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.""" + h_contents1 = ''' + typedef enum { + FIRST, + } Header1; + ''' + + h_contents2 = ''' + typedef enum { + SECOND, + } Header2; + ''' + + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='.template') as template_file, \ + 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 template and headers. + template_file.write('template'.encode('utf-8')) + h_file1.write(h_contents1.encode('utf-8')) + h_file2.write(h_contents2.encode('utf-8')) + + template_file.flush() + h_file1.flush() + h_file2.flush() + + # Run glib-mkenums with the headers in one order, and then again + # in another order. + info1 = self.runMkenums('--template', template_file.name, + h_file1.name, h_file2.name) + info1.check_returncode() + out1 = info1.stdout.decode('utf-8').strip() + self.assertEqual('', info1.stderr.decode('utf-8').strip()) + + info2 = self.runMkenums('--template', template_file.name, + h_file2.name, h_file1.name) + info2.check_returncode() + out2 = info2.stdout.decode('utf-8').strip() + self.assertEqual('', info2.stderr.decode('utf-8').strip()) + + # The output should be the same. + self.assertEqual(out1, out2) + + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gobject/tests/taptestrunner.py b/gobject/tests/taptestrunner.py new file mode 100644 index 000000000..efd41dd25 --- /dev/null +++ b/gobject/tests/taptestrunner.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# coding=utf-8 + +# Copyright (c) 2015 Remko Tronçon (https://el-tramo.be) +# Copied from https://github.com/remko/pycotap/ +# +# Released under the MIT license +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import unittest +import sys +import base64 +if sys.hexversion >= 0x03000000: + from io import StringIO +else: + from StringIO import StringIO + +# Log modes +class LogMode(object) : + LogToError, LogToDiagnostics, LogToYAML, LogToAttachment = range(4) + + +class TAPTestResult(unittest.TestResult): + def __init__(self, output_stream, error_stream, message_log, test_output_log): + super(TAPTestResult, self).__init__(self, output_stream) + self.output_stream = output_stream + self.error_stream = error_stream + self.orig_stdout = None + self.orig_stderr = None + self.message = None + self.test_output = None + self.message_log = message_log + self.test_output_log = test_output_log + self.output_stream.write("TAP version 13\n") + self._set_streams() + + def printErrors(self): + self.print_raw("1..%d\n" % self.testsRun) + self._reset_streams() + + def _set_streams(self): + self.orig_stdout = sys.stdout + self.orig_stderr = sys.stderr + if self.message_log == LogMode.LogToError: + self.message = self.error_stream + else: + self.message = StringIO() + if self.test_output_log == LogMode.LogToError: + self.test_output = self.error_stream + else: + self.test_output = StringIO() + + if self.message_log == self.test_output_log: + self.test_output = self.message + sys.stdout = sys.stderr = self.test_output + + def _reset_streams(self): + sys.stdout = self.orig_stdout + sys.stderr = self.orig_stderr + + + def print_raw(self, text): + self.output_stream.write(text) + self.output_stream.flush() + + def print_result(self, result, test, directive = None): + self.output_stream.write("%s %d %s" % (result, self.testsRun, test.id())) + if directive: + self.output_stream.write(" # " + directive) + self.output_stream.write("\n") + self.output_stream.flush() + + def ok(self, test, directive = None): + self.print_result("ok", test, directive) + + def not_ok(self, test): + self.print_result("not ok", test) + + def startTest(self, test): + super(TAPTestResult, self).startTest(test) + + def stopTest(self, test): + super(TAPTestResult, self).stopTest(test) + if self.message_log == self.test_output_log: + logs = [(self.message_log, self.message, "output")] + else: + logs = [ + (self.test_output_log, self.test_output, "test_output"), + (self.message_log, self.message, "message") + ] + for log_mode, log, log_name in logs: + if log_mode != LogMode.LogToError: + output = log.getvalue() + if len(output): + if log_mode == LogMode.LogToYAML: + self.print_raw(" ---\n") + self.print_raw(" " + log_name + ": |\n") + self.print_raw(" " + output.rstrip().replace("\n", "\n ") + "\n") + self.print_raw(" ...\n") + elif log_mode == LogMode.LogToAttachment: + self.print_raw(" ---\n") + self.print_raw(" " + log_name + ":\n") + self.print_raw(" File-Name: " + log_name + ".txt\n") + self.print_raw(" File-Type: text/plain\n") + self.print_raw(" File-Content: " + base64.b64encode(output) + "\n") + self.print_raw(" ...\n") + else: + self.print_raw("# " + output.rstrip().replace("\n", "\n# ") + "\n") + log.truncate(0) + + def addSuccess(self, test): + super(TAPTestResult, self).addSuccess(test) + self.ok(test) + + def addError(self, test, err): + super(TAPTestResult, self).addError(test, err) + self.message.write(self.errors[-1][1] + "\n") + self.not_ok(test) + + def addFailure(self, test, err): + super(TAPTestResult, self).addFailure(test, err) + self.message.write(self.failures[-1][1] + "\n") + self.not_ok(test) + + def addSkip(self, test, reason): + super(TAPTestResult, self).addSkip(test, reason) + self.ok(test, "SKIP " + reason) + + def addExpectedFailure(self, test, err): + super(TAPTestResult, self).addExpectedFailure(test, err) + self.ok(test) + + def addUnexpectedSuccess(self, test): + super(TAPTestResult, self).addUnexpectedSuccess(test) + self.message.write("Unexpected success" + "\n") + self.not_ok(test) + + +class TAPTestRunner(object): + def __init__(self, + message_log = LogMode.LogToYAML, + test_output_log = LogMode.LogToDiagnostics, + output_stream = sys.stdout, error_stream = sys.stderr): + self.output_stream = output_stream + self.error_stream = error_stream + self.message_log = message_log + self.test_output_log = test_output_log + + def run(self, test): + result = TAPTestResult( + self.output_stream, + self.error_stream, + self.message_log, + self.test_output_log) + test(result) + result.printErrors() + + return result |