glib/gobject/tests/mkenums.py
Marco Trevisan (Treviño) 00ebf4e1eb tests/lib: Add a new unittest type to simplify launching test programs
We were reusing the same logic everywhere, while we can just reuse an
unique class to base our tests on that avoids having to copy-and-paste
code for no good reason
2025-02-11 18:51:15 +01:00

777 lines
21 KiB
Python

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright © 2018 Endless Mobile, Inc.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# 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 tempfile
import textwrap
import unittest
import taptestrunner
import testprogramrunner
class TestMkenums(testprogramrunner.TestProgramRunner):
"""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.
"""
PROGRAM_NAME = "glib-mkenums"
PROGRAM_TYPE = testprogramrunner.ProgramType.INTERPRETED
rspfile = False
def setUp(self):
super().setUp()
print("rspfile: {}".format(self.rspfile))
def _write_rspfile(self, argv):
import shlex
with tempfile.NamedTemporaryFile(
dir=self.tmpdir.name, mode="w", delete=False
) as f:
contents = " ".join([shlex.quote(arg) for arg in argv])
print("Response file contains:", contents)
f.write(contents)
f.flush()
return f.name
def runMkenums(self, *args):
argv = list(args)
if self.rspfile:
rspfile = self._write_rspfile(args)
argv = ["@" + rspfile]
return self.runTestProgram(argv)
def _getSubs(self):
# Known substitutions for standard boilerplate
return {
"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",
}
def runMkenumsWithTemplate(self, template_contents, *args):
with tempfile.NamedTemporaryFile(
dir=self.tmpdir.name, suffix=".template", delete=False
) 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
/*** 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@
enumsince: @enumsince@
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@
enumsince: @enumsince@
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@
enumsince: @enumsince@
type: @type@
Type: @Type@
TYPE: @TYPE@
/*** END value-tail ***/
/*** BEGIN comment ***/
comment
comment: @comment@
/*** END comment ***/
/*** BEGIN file-tail ***/
file-tail
/*** 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", delete=False
) 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,
enum_since,
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,
"enum_since": enum_since,
"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
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}
enumsince: {enum_since}
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}
enumsince: {enum_since}
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}
enumsince: {enum_since}
type: {type_lower}
Type: {type_camel}
TYPE: {type_upper}
file-tail
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_no_args(self):
"""Test running with no arguments at all."""
result = self.runMkenums()
self.assertEqual("", result.err)
self.assertEqual(
"""/* {standard_top_comment} */
/* {standard_bottom_comment} */""".format(
**result.subs
),
result.out.strip(),
)
def test_empty_template(self):
"""Test running with an empty template and no header files."""
result = self.runMkenumsWithTemplate("")
self.assertEqual("", result.err)
self.assertEqual(
"""/* {standard_top_comment} */
/* {standard_bottom_comment} */""".format(
**result.subs
),
result.out.strip(),
)
def test_no_headers(self):
"""Test running with a complete template, but no header files."""
result = self.runMkenumsWithAllSubstitutions()
self.assertEqual("", result.err)
self.assertEqual(
"""
comment
comment: {standard_top_comment}
file-header
file-tail
comment
comment: {standard_bottom_comment}
""".format(
**result.subs
).strip(),
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
file-tail
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", delete=False
) as h_file1, tempfile.NamedTemporaryFile(
dir=self.tmpdir.name, suffix="2.h", delete=False
) 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",
)
def test_with_double_quotes(self):
"""Test trigraphs with double-quoted expressions. Issue #65."""
h_contents = """
typedef enum {
FOO_VALUE /*< nick="eek, a comma" >*/
} Foo;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"Foo",
"foo_",
"FOO_",
"",
"FOO",
"",
"enum",
"Enum",
"ENUM",
"FOO_VALUE",
"eek, a comma",
"0",
)
def test_filename_basename_in_fhead_ftail(self):
template_contents = """
/*** BEGIN file-header ***/
file-header
filename: @filename@
basename: @basename@
/*** END file-header ***/
/*** BEGIN comment ***/
comment
comment: @comment@
/*** END comment ***/
/*** BEGIN file-tail ***/
file-tail
filename: @filename@
basename: @basename@
/*** END file-tail ***/"""
result = self.runMkenumsWithTemplate(template_contents)
self.assertEqual(
textwrap.dedent(
"""
WARNING: @filename@ used in file-header section.
WARNING: @basename@ used in file-header section.
WARNING: @filename@ used in file-tail section.
WARNING: @basename@ used in file-tail section.
"""
).strip(),
result.err,
)
self.assertEqual(
"""
comment
comment: {standard_top_comment}
file-header
filename: @filename@
basename: @basename@
file-tail
filename: @filename@
basename: @basename@
comment
comment: {standard_bottom_comment}
""".format(
**result.subs
).strip(),
result.out,
)
def test_since(self):
"""Test user-provided 'since' version handling
https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1492"""
h_contents = """
typedef enum { /*< since=1.0 >*/
QMI_WMS_MESSAGE_PROTOCOL_CDMA = 0,
} QmiWmsMessageProtocol;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"QmiWmsMessageProtocol",
"qmi_wms_message_protocol",
"QMI_WMS_MESSAGE_PROTOCOL",
"WMS_MESSAGE_PROTOCOL",
"QMI",
"1.0",
"enum",
"Enum",
"ENUM",
"QMI_WMS_MESSAGE_PROTOCOL_CDMA",
"cdma",
"0",
)
def test_enum_private_public(self):
"""Test private/public enums. Bug #782162."""
h_contents1 = """
typedef enum {
ENUM_VALUE_PUBLIC1,
/*< private >*/
ENUM_VALUE_PRIVATE,
} SomeEnumA
"""
h_contents2 = """
typedef enum {
/*< private >*/
ENUM_VALUE_PRIVATE,
/*< public >*/
ENUM_VALUE_PUBLIC2,
} SomeEnumB;
"""
result = self.runMkenumsWithHeader(h_contents1)
self.maxDiff = None
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"SomeEnumA",
"some_enum_a",
"SOME_ENUM_A",
"ENUM_A",
"SOME",
"",
"enum",
"Enum",
"ENUM",
"ENUM_VALUE_PUBLIC1",
"public1",
"0",
)
result = self.runMkenumsWithHeader(h_contents2)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"SomeEnumB",
"some_enum_b",
"SOME_ENUM_B",
"ENUM_B",
"SOME",
"",
"enum",
"Enum",
"ENUM",
"ENUM_VALUE_PUBLIC2",
"public2",
"1",
)
def test_available_in(self):
"""Test GLIB_AVAILABLE_ENUMERATOR_IN_2_68 handling
https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
h_contents = """
typedef enum {
G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_AVAILABLE_ENUMERATOR_IN_2_68 = (1<<2)
} GDBusServerFlags;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"GDBusServerFlags",
"g_dbus_server_flags",
"G_DBUS_SERVER_FLAGS",
"DBUS_SERVER_FLAGS",
"G",
"",
"flags",
"Flags",
"FLAGS",
"G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
"user",
"4",
)
def test_deprecated_in(self):
"""Test GLIB_DEPRECATED_ENUMERATOR_IN_2_68 handling
https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
h_contents = """
typedef enum {
G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_DEPRECATED_ENUMERATOR_IN_2_68 = (1<<2)
} GDBusServerFlags;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"GDBusServerFlags",
"g_dbus_server_flags",
"G_DBUS_SERVER_FLAGS",
"DBUS_SERVER_FLAGS",
"G",
"",
"flags",
"Flags",
"FLAGS",
"G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
"user",
"4",
)
def test_deprecated_in_for(self):
"""Test GLIB_DEPRECATED_ENUMERATOR_IN_2_68_FOR() handling
https://gitlab.gnome.org/GNOME/glib/-/issues/2327"""
h_contents = """
typedef enum {
G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_DEPRECATED_ENUMERATOR_IN_2_68_FOR(G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER2) = (1<<2)
} GDBusServerFlags;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"GDBusServerFlags",
"g_dbus_server_flags",
"G_DBUS_SERVER_FLAGS",
"DBUS_SERVER_FLAGS",
"G",
"",
"flags",
"Flags",
"FLAGS",
"G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER",
"user",
"4",
)
def test_enum_symbolic_expression(self):
"""Test use of symbol in value expression."""
h_contents = """
typedef enum {
/*< private >*/
ENUM_VALUE_PRIVATE = 5,
/*< public >*/
ENUM_VALUE_PUBLIC = ENUM_VALUE_PRIVATE + 2,
} TestSymbolicEnum;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"TestSymbolicEnum",
"test_symbolic_enum",
"TEST_SYMBOLIC_ENUM",
"SYMBOLIC_ENUM",
"TEST",
"",
"enum",
"Enum",
"ENUM",
"ENUM_VALUE_PUBLIC",
"public",
"7",
)
def test_comma_in_enum_value(self):
"""Test use of comma in enum value."""
h_contents = """
typedef enum {
ENUM_VALUE_WITH_COMMA = ',',
} TestCommaEnum;
"""
result = self.runMkenumsWithHeader(h_contents)
self.assertEqual("", result.err)
self.assertSingleEnum(
result,
"TestCommaEnum",
"test_comma_enum",
"TEST_COMMA_ENUM",
"COMMA_ENUM",
"TEST",
"",
"enum",
"Enum",
"ENUM",
"ENUM_VALUE_WITH_COMMA",
"comma",
44,
)
class TestRspMkenums(TestMkenums):
"""Run all tests again in @rspfile mode"""
rspfile = True
if __name__ == "__main__":
unittest.main(testRunner=taptestrunner.TAPTestRunner())