mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-12 15:36:17 +01:00
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
This commit is contained in:
parent
c0e6fa4f19
commit
e8d755eb8e
@ -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'],
|
||||
)
|
364
gobject/tests/mkenums.py
Normal file
364
gobject/tests/mkenums.py
Normal file
@ -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())
|
176
gobject/tests/taptestrunner.py
Normal file
176
gobject/tests/taptestrunner.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user