[glib: 2/4] tests: Add initial test framework for glib-mkenums



commit e8d755eb8ecc70a8cae7e4de10684e77c21aaad5
Author: Philip Withnall <withnall endlessm com>
Date:   Wed May 2 11:05:20 2018 +0100

    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

 gobject/tests/meson.build      |  11 ++
 gobject/tests/mkenums.py       | 364 +++++++++++++++++++++++++++++++++++++++++
 gobject/tests/taptestrunner.py | 176 ++++++++++++++++++++
 3 files changed, 551 insertions(+)
---
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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]