facf0c92e0
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
1654 lines
60 KiB
Python
1654 lines
60 KiB
Python
#!/usr/bin/python3
|
||
# -*- coding: utf-8 -*-
|
||
#
|
||
# Copyright © 2018, 2019 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 gdbus-codegen utility."""
|
||
|
||
import os
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import textwrap
|
||
import unittest
|
||
import xml.etree.ElementTree as ET
|
||
|
||
import taptestrunner
|
||
import testprogramrunner
|
||
|
||
# Disable line length warnings as wrapping the C code templates would be hard
|
||
# flake8: noqa: E501
|
||
|
||
|
||
class TestCodegen(testprogramrunner.TestProgramRunner):
|
||
"""Integration test for running gdbus-codegen.
|
||
|
||
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 gdbus-codegen utility, its
|
||
handling of command line arguments, its exit statuses, and its handling of
|
||
various C source codes. In future we could split out tests for the core
|
||
parsing and generation code of gdbus-codegen into separate unit tests, and
|
||
just test command line behaviour in this integration test.
|
||
"""
|
||
|
||
PROGRAM_NAME = "gdbus-codegen"
|
||
PROGRAM_TYPE = testprogramrunner.ProgramType.INTERPRETED
|
||
|
||
ARGUMENTS_TYPES = {
|
||
"b": {"value_type": "boolean"},
|
||
"y": {"value_type": "uchar"},
|
||
"n": {"value_type": "int"},
|
||
"q": {"value_type": "uint"},
|
||
"i": {"value_type": "int"},
|
||
"u": {"value_type": "uint"},
|
||
"x": {"value_type": "int64", "lacks_marshaller": True},
|
||
"t": {"value_type": "uint64", "lacks_marshaller": True},
|
||
"d": {"value_type": "double"},
|
||
"s": {"value_type": "string"},
|
||
"o": {"value_type": "string"},
|
||
"g": {"value_type": "string"},
|
||
"h": {"value_type": "variant"},
|
||
"ay": {"value_type": "string"},
|
||
"as": {"value_type": "boxed"},
|
||
"ao": {"value_type": "boxed"},
|
||
"aay": {"value_type": "boxed"},
|
||
"asv": {"value_type": "variant", "variant_type": "a{sv}"},
|
||
}
|
||
|
||
def runCodegen(self, *args, **kwargs):
|
||
return self.runTestProgram(args, **kwargs)
|
||
|
||
def _getSubs(self):
|
||
# Known substitutions for standard boilerplate
|
||
return {
|
||
"standard_top_comment": "/*\n"
|
||
" * This file is generated by gdbus-codegen, do not modify it.\n"
|
||
" *\n"
|
||
" * The license of this code is the same as for the D-Bus interface description\n"
|
||
" * it was derived from. Note that it links to GLib, so must comply with the\n"
|
||
" * LGPL linking clauses.\n"
|
||
" */",
|
||
"standard_config_h_include": "#ifdef HAVE_CONFIG_H\n"
|
||
'# include "config.h"\n'
|
||
"#endif",
|
||
"standard_header_includes": "#include <string.h>\n"
|
||
"#ifdef G_OS_UNIX\n"
|
||
"# include <gio/gunixfdlist.h>\n"
|
||
"#endif",
|
||
"interface_info_header_includes": "#include <string.h>",
|
||
"private_gvalues_getters": """#ifdef G_ENABLE_DEBUG
|
||
#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
|
||
#define g_marshal_value_peek_char(v) g_value_get_schar (v)
|
||
#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
|
||
#define g_marshal_value_peek_int(v) g_value_get_int (v)
|
||
#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
|
||
#define g_marshal_value_peek_long(v) g_value_get_long (v)
|
||
#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
|
||
#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
|
||
#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
|
||
#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
|
||
#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
|
||
#define g_marshal_value_peek_float(v) g_value_get_float (v)
|
||
#define g_marshal_value_peek_double(v) g_value_get_double (v)
|
||
#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
|
||
#define g_marshal_value_peek_param(v) g_value_get_param (v)
|
||
#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
|
||
#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
|
||
#define g_marshal_value_peek_object(v) g_value_get_object (v)
|
||
#define g_marshal_value_peek_variant(v) g_value_get_variant (v)
|
||
#else /* !G_ENABLE_DEBUG */
|
||
/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
|
||
* Do not access GValues directly in your code. Instead, use the
|
||
* g_value_get_*() functions
|
||
*/
|
||
#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
|
||
#define g_marshal_value_peek_char(v) (v)->data[0].v_int
|
||
#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
|
||
#define g_marshal_value_peek_int(v) (v)->data[0].v_int
|
||
#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
|
||
#define g_marshal_value_peek_long(v) (v)->data[0].v_long
|
||
#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
|
||
#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
|
||
#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
|
||
#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
|
||
#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
|
||
#define g_marshal_value_peek_float(v) (v)->data[0].v_float
|
||
#define g_marshal_value_peek_double(v) (v)->data[0].v_double
|
||
#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
|
||
#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
|
||
#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
|
||
#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
|
||
#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
|
||
#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer
|
||
#endif /* !G_ENABLE_DEBUG */""",
|
||
"standard_typedefs_and_helpers": "typedef struct\n"
|
||
"{\n"
|
||
" GDBusArgInfo parent_struct;\n"
|
||
" gboolean use_gvariant;\n"
|
||
"} _ExtendedGDBusArgInfo;\n"
|
||
"\n"
|
||
"typedef struct\n"
|
||
"{\n"
|
||
" GDBusMethodInfo parent_struct;\n"
|
||
" const gchar *signal_name;\n"
|
||
" gboolean pass_fdlist;\n"
|
||
"} _ExtendedGDBusMethodInfo;\n"
|
||
"\n"
|
||
"typedef struct\n"
|
||
"{\n"
|
||
" GDBusSignalInfo parent_struct;\n"
|
||
" const gchar *signal_name;\n"
|
||
"} _ExtendedGDBusSignalInfo;\n"
|
||
"\n"
|
||
"typedef struct\n"
|
||
"{\n"
|
||
" GDBusPropertyInfo parent_struct;\n"
|
||
" const gchar *hyphen_name;\n"
|
||
" guint use_gvariant : 1;\n"
|
||
" guint emits_changed_signal : 1;\n"
|
||
"} _ExtendedGDBusPropertyInfo;\n"
|
||
"\n"
|
||
"typedef struct\n"
|
||
"{\n"
|
||
" GDBusInterfaceInfo parent_struct;\n"
|
||
" const gchar *hyphen_name;\n"
|
||
"} _ExtendedGDBusInterfaceInfo;\n"
|
||
"\n"
|
||
"typedef struct\n"
|
||
"{\n"
|
||
" const _ExtendedGDBusPropertyInfo *info;\n"
|
||
" guint prop_id;\n"
|
||
" GValue orig_value; /* the value before the change */\n"
|
||
"} ChangedProperty;\n"
|
||
"\n"
|
||
"static void\n"
|
||
"_changed_property_free (ChangedProperty *data)\n"
|
||
"{\n"
|
||
" g_value_unset (&data->orig_value);\n"
|
||
" g_free (data);\n"
|
||
"}\n"
|
||
"\n"
|
||
"static gboolean\n"
|
||
"_g_strv_equal0 (gchar **a, gchar **b)\n"
|
||
"{\n"
|
||
" gboolean ret = FALSE;\n"
|
||
" guint n;\n"
|
||
" if (a == NULL && b == NULL)\n"
|
||
" {\n"
|
||
" ret = TRUE;\n"
|
||
" goto out;\n"
|
||
" }\n"
|
||
" if (a == NULL || b == NULL)\n"
|
||
" goto out;\n"
|
||
" if (g_strv_length (a) != g_strv_length (b))\n"
|
||
" goto out;\n"
|
||
" for (n = 0; a[n] != NULL; n++)\n"
|
||
" if (g_strcmp0 (a[n], b[n]) != 0)\n"
|
||
" goto out;\n"
|
||
" ret = TRUE;\n"
|
||
"out:\n"
|
||
" return ret;\n"
|
||
"}\n"
|
||
"\n"
|
||
"static gboolean\n"
|
||
"_g_variant_equal0 (GVariant *a, GVariant *b)\n"
|
||
"{\n"
|
||
" gboolean ret = FALSE;\n"
|
||
" if (a == NULL && b == NULL)\n"
|
||
" {\n"
|
||
" ret = TRUE;\n"
|
||
" goto out;\n"
|
||
" }\n"
|
||
" if (a == NULL || b == NULL)\n"
|
||
" goto out;\n"
|
||
" ret = g_variant_equal (a, b);\n"
|
||
"out:\n"
|
||
" return ret;\n"
|
||
"}\n"
|
||
"\n"
|
||
"G_GNUC_UNUSED static gboolean\n"
|
||
"_g_value_equal (const GValue *a, const GValue *b)\n"
|
||
"{\n"
|
||
" gboolean ret = FALSE;\n"
|
||
" g_assert (G_VALUE_TYPE (a) == G_VALUE_TYPE (b));\n"
|
||
" switch (G_VALUE_TYPE (a))\n"
|
||
" {\n"
|
||
" case G_TYPE_BOOLEAN:\n"
|
||
" ret = (g_value_get_boolean (a) == g_value_get_boolean (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_UCHAR:\n"
|
||
" ret = (g_value_get_uchar (a) == g_value_get_uchar (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_INT:\n"
|
||
" ret = (g_value_get_int (a) == g_value_get_int (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_UINT:\n"
|
||
" ret = (g_value_get_uint (a) == g_value_get_uint (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_INT64:\n"
|
||
" ret = (g_value_get_int64 (a) == g_value_get_int64 (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_UINT64:\n"
|
||
" ret = (g_value_get_uint64 (a) == g_value_get_uint64 (b));\n"
|
||
" break;\n"
|
||
" case G_TYPE_DOUBLE:\n"
|
||
" {\n"
|
||
" /* Avoid -Wfloat-equal warnings by doing a direct bit compare */\n"
|
||
" gdouble da = g_value_get_double (a);\n"
|
||
" gdouble db = g_value_get_double (b);\n"
|
||
" ret = memcmp (&da, &db, sizeof (gdouble)) == 0;\n"
|
||
" }\n"
|
||
" break;\n"
|
||
" case G_TYPE_STRING:\n"
|
||
" ret = (g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0);\n"
|
||
" break;\n"
|
||
" case G_TYPE_VARIANT:\n"
|
||
" ret = _g_variant_equal0 (g_value_get_variant (a), g_value_get_variant (b));\n"
|
||
" break;\n"
|
||
" default:\n"
|
||
" if (G_VALUE_TYPE (a) == G_TYPE_STRV)\n"
|
||
" ret = _g_strv_equal0 (g_value_get_boxed (a), g_value_get_boxed (b));\n"
|
||
" else\n"
|
||
' g_critical ("_g_value_equal() does not handle type %s", g_type_name (G_VALUE_TYPE (a)));\n'
|
||
" break;\n"
|
||
" }\n"
|
||
" return ret;\n"
|
||
"}",
|
||
}
|
||
|
||
def runCodegenWithInterface(self, interface_contents, *args, **kwargs):
|
||
with tempfile.NamedTemporaryFile(
|
||
dir=self.tmpdir.name, suffix=".xml", delete=False
|
||
) as interface_file:
|
||
# Write out the interface.
|
||
interface_file.write(interface_contents.encode("utf-8"))
|
||
print(interface_file.name + ":", interface_contents)
|
||
interface_file.flush()
|
||
|
||
return self.runCodegen(interface_file.name, *args, **kwargs)
|
||
|
||
def get_codegen_ext_path(self):
|
||
return os.path.join(
|
||
os.path.dirname(os.path.realpath(__file__)), "codegen-test-extension.py"
|
||
)
|
||
|
||
def test_help(self):
|
||
"""Test the --help argument."""
|
||
result = self.runCodegen("--help")
|
||
self.assertIn("usage: gdbus-codegen", result.out)
|
||
|
||
def test_no_args(self):
|
||
"""Test running with no arguments at all."""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegen()
|
||
|
||
def test_empty_interface_header(self):
|
||
"""Test generating a header with an empty interface file."""
|
||
result = self.runCodegenWithInterface("", "--output", "-", "--header")
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
#ifndef __STDOUT__
|
||
#define __STDOUT__
|
||
|
||
#include <gio/gio.h>
|
||
|
||
G_BEGIN_DECLS
|
||
|
||
|
||
G_END_DECLS
|
||
|
||
#endif /* __STDOUT__ */""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_empty_interface_info_header(self):
|
||
"""Test generating a header with an empty interface file."""
|
||
result = self.runCodegenWithInterface(
|
||
"", "--output", "-", "--interface-info-header"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
#ifndef __STDOUT__
|
||
#define __STDOUT__
|
||
|
||
#include <gio/gio.h>
|
||
|
||
G_BEGIN_DECLS
|
||
|
||
|
||
G_END_DECLS
|
||
|
||
#endif /* __STDOUT__ */""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_empty_interface_body(self):
|
||
"""Test generating a body with an empty interface file."""
|
||
result = self.runCodegenWithInterface("", "--output", "-", "--body")
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
{standard_config_h_include}
|
||
|
||
{standard_header_includes}
|
||
|
||
{private_gvalues_getters}
|
||
|
||
{standard_typedefs_and_helpers}""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_empty_interface_info_body(self):
|
||
"""Test generating a body with an empty interface file."""
|
||
result = self.runCodegenWithInterface(
|
||
"", "--output", "-", "--interface-info-body"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
{standard_config_h_include}
|
||
|
||
{interface_info_header_includes}""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_reproducible(self):
|
||
"""Test builds are reproducible regardless of file ordering."""
|
||
xml_contents1 = """
|
||
<node>
|
||
<interface name="com.acme.Coyote">
|
||
<method name="Run"/>
|
||
<method name="Sleep"/>
|
||
<method name="Attack"/>
|
||
<signal name="Surprised"/>
|
||
<property name="Mood" type="s" access="read"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
|
||
xml_contents2 = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<method name="RandomMethod"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
|
||
with tempfile.NamedTemporaryFile(
|
||
dir=self.tmpdir.name, suffix="1.xml", delete=False
|
||
) as xml_file1, tempfile.NamedTemporaryFile(
|
||
dir=self.tmpdir.name, suffix="2.xml", delete=False
|
||
) as xml_file2:
|
||
# Write out the interfaces.
|
||
xml_file1.write(xml_contents1.encode("utf-8"))
|
||
xml_file2.write(xml_contents2.encode("utf-8"))
|
||
|
||
xml_file1.flush()
|
||
xml_file2.flush()
|
||
|
||
# Repeat this for headers and bodies.
|
||
for header_or_body in ["--header", "--body"]:
|
||
# Run gdbus-codegen with the interfaces in one order, and then
|
||
# again in another order.
|
||
result1 = self.runCodegen(
|
||
xml_file1.name,
|
||
xml_file2.name,
|
||
"--output",
|
||
"-",
|
||
header_or_body,
|
||
)
|
||
self.assertEqual("", result1.err)
|
||
|
||
result2 = self.runCodegen(
|
||
xml_file2.name,
|
||
xml_file1.name,
|
||
"--output",
|
||
"-",
|
||
header_or_body,
|
||
)
|
||
self.assertEqual("", result2.err)
|
||
|
||
# The output should be the same.
|
||
self.assertEqual(result1.out, result2.out)
|
||
|
||
def test_generate_docbook(self):
|
||
"""Test the basic functionality of the docbook generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<method name="RandomMethod"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-docbook",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.xml", "r") as f:
|
||
xml_data = f.readlines()
|
||
self.assertNotEqual(len(xml_data), 0)
|
||
|
||
def test_generate_md(self):
|
||
"""Test the basic functionality of the markdown generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<method name="RandomMethod"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-md",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.md", "r") as f:
|
||
rst = f.readlines()
|
||
self.assertNotEqual(len(rst), 0)
|
||
|
||
def test_generate_rst(self):
|
||
"""Test the basic functionality of the rst generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<method name="RandomMethod"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-rst",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.rst", "r") as f:
|
||
rst = f.readlines()
|
||
self.assertNotEqual(len(rst), 0)
|
||
|
||
def test_generate_rst_method(self):
|
||
"""Test generating a method documentation with the rst generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<!-- RandomMethod:
|
||
|
||
A random test method.
|
||
-->
|
||
<method name="RandomMethod"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-rst",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.rst", "r") as f:
|
||
rst = f.read()
|
||
self.assertIn(
|
||
textwrap.dedent(
|
||
"""
|
||
-------
|
||
Methods
|
||
-------
|
||
|
||
.. _org.project.Bar.Frobnicator.RandomMethod:
|
||
|
||
org.project.Bar.Frobnicator.RandomMethod
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
::
|
||
|
||
RandomMethod ()
|
||
|
||
|
||
A random test method."""
|
||
),
|
||
rst,
|
||
)
|
||
|
||
def test_generate_rst_signal(self):
|
||
"""Test generating a signal documentation with the rst generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<!-- RandomSignal:
|
||
|
||
A random test signal.
|
||
-->
|
||
<signal name="RandomSignal"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-rst",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.rst", "r") as f:
|
||
rst = f.read()
|
||
self.assertIn(
|
||
textwrap.dedent(
|
||
"""
|
||
-------
|
||
Signals
|
||
-------
|
||
|
||
.. _org.project.Bar.Frobnicator::RandomSignal:
|
||
|
||
org.project.Bar.Frobnicator::RandomSignal
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
::
|
||
|
||
RandomSignal ()
|
||
|
||
|
||
A random test signal."""
|
||
),
|
||
rst,
|
||
)
|
||
|
||
def test_generate_rst_property(self):
|
||
"""Test generating a property documentation with the rst generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<!-- RandomProperty:
|
||
|
||
A random test property.
|
||
-->
|
||
<property type="s" name="RandomProperty" access="read"/>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-rst",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.rst", "r") as f:
|
||
rst = f.read()
|
||
self.assertIn(
|
||
textwrap.dedent(
|
||
"""
|
||
----------
|
||
Properties
|
||
----------
|
||
|
||
.. _org.project.Bar.Frobnicator:RandomProperty:
|
||
|
||
org.project.Bar.Frobnicator:RandomProperty
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
::
|
||
|
||
RandomProperty readable s
|
||
|
||
|
||
A random test property."""
|
||
),
|
||
rst,
|
||
)
|
||
|
||
def test_glib_min_required_invalid(self):
|
||
"""Test running with an invalid --glib-min-required."""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--body",
|
||
"--glib-min-required",
|
||
"hello mum",
|
||
)
|
||
|
||
def test_glib_min_required_too_low(self):
|
||
"""Test running with a --glib-min-required which is too low (and hence
|
||
probably a typo)."""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegenWithInterface(
|
||
"", "--output", "-", "--body", "--glib-min-required", "2.6"
|
||
)
|
||
|
||
def test_glib_min_required_major_only(self):
|
||
"""Test running with a --glib-min-required which contains only a major version."""
|
||
result = self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-min-required",
|
||
"3",
|
||
"--glib-max-allowed",
|
||
"3.2",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertNotEqual("", result.out.strip())
|
||
|
||
def test_glib_min_required_with_micro(self):
|
||
"""Test running with a --glib-min-required which contains a micro version."""
|
||
result = self.runCodegenWithInterface(
|
||
"", "--output", "-", "--header", "--glib-min-required", "2.46.2"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertNotEqual("", result.out.strip())
|
||
|
||
def test_glib_max_allowed_too_low(self):
|
||
"""Test running with a --glib-max-allowed which is too low (and hence
|
||
probably a typo)."""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegenWithInterface(
|
||
"", "--output", "-", "--body", "--glib-max-allowed", "2.6"
|
||
)
|
||
|
||
def test_glib_max_allowed_major_only(self):
|
||
"""Test running with a --glib-max-allowed which contains only a major version."""
|
||
result = self.runCodegenWithInterface(
|
||
"", "--output", "-", "--header", "--glib-max-allowed", "3"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertNotEqual("", result.out.strip())
|
||
|
||
def test_glib_max_allowed_with_micro(self):
|
||
"""Test running with a --glib-max-allowed which contains a micro version."""
|
||
result = self.runCodegenWithInterface(
|
||
"", "--output", "-", "--header", "--glib-max-allowed", "2.46.2"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertNotEqual("", result.out.strip())
|
||
|
||
def test_glib_max_allowed_unstable(self):
|
||
"""Test running with a --glib-max-allowed which is unstable. It should
|
||
be rounded up to the next stable version number, and hence should not
|
||
end up less than --glib-min-required."""
|
||
result = self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-max-allowed",
|
||
"2.63",
|
||
"--glib-min-required",
|
||
"2.64",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertNotEqual("", result.out.strip())
|
||
|
||
def test_glib_max_allowed_less_than_min_required(self):
|
||
"""Test running with a --glib-max-allowed which is less than
|
||
--glib-min-required."""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--body",
|
||
"--glib-max-allowed",
|
||
"2.62",
|
||
"--glib-min-required",
|
||
"2.64",
|
||
)
|
||
|
||
def test_dbus_types(self):
|
||
bad_types = [
|
||
"{vs}", # Bad dictionary key type
|
||
"(ss(s{{sv}s}))", # Bad dictionary key types
|
||
"{s", # Unterminated dictionary
|
||
"(s{sss})", # Unterminated dictionary
|
||
"z", # Bad type
|
||
"(ssms)", # Bad type
|
||
"(", # Unterminated tuple
|
||
"(((ss))", # Unterminated tuple
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", # Too much recursion
|
||
"(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((("
|
||
"(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((s))"
|
||
"))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))"
|
||
"))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))", # Too much recursion
|
||
"{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{"
|
||
"{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{sv}"
|
||
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"
|
||
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}", # Too much recursion
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaa{sv})", # Too much recursion
|
||
"(ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
||
"ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
||
"ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"
|
||
"ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss)", # Too long
|
||
]
|
||
for t in bad_types:
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="BadTypes">
|
||
<property type="{t}" name="BadPropertyType" access="read" />
|
||
</interface>
|
||
</node>"""
|
||
with self.assertRaises(subprocess.CalledProcessError):
|
||
self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
good_types = [
|
||
"si{s{b(ybnqiuxtdh)}}{yv}{nv}{dv}",
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", # 128 Levels of recursion
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(aaaaaaaaaaaaaaaaa{sv})", # 128 Levels of recursion
|
||
]
|
||
for t in good_types:
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="GoodTypes">
|
||
<property type="{t}" name="GoodPropertyType" access="read" />
|
||
</interface>
|
||
</node>"""
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--body"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
|
||
def test_unix_fd_types_and_annotations(self):
|
||
"""Test an interface with `h` arguments, no annotation, and GLib < 2.64.
|
||
|
||
See issue #1726.
|
||
"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="FDPassing">
|
||
<method name="HelloFD">
|
||
<annotation name="org.gtk.GDBus.C.UnixFD" value="1"/>
|
||
<arg name="greeting" direction="in" type="s"/>
|
||
<arg name="response" direction="out" type="s"/>
|
||
</method>
|
||
<method name="NoAnnotation">
|
||
<arg name="greeting" direction="in" type="h"/>
|
||
<arg name="greeting_locale" direction="in" type="s"/>
|
||
<arg name="response" direction="out" type="h"/>
|
||
<arg name="response_locale" direction="out" type="s"/>
|
||
</method>
|
||
<method name="NoAnnotationNested">
|
||
<arg name="files" type="a{sh}" direction="in"/>
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
# Try without specifying --glib-min-required.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--header"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GUnixFDList"), 6)
|
||
|
||
# Specify an old --glib-min-required.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-min-required",
|
||
"2.32",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GUnixFDList"), 6)
|
||
|
||
# Specify a --glib-min-required ≥ 2.64. There should be more
|
||
# mentions of `GUnixFDList` now, since the annotation is not needed to
|
||
# trigger its use.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-min-required",
|
||
"2.64",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GUnixFDList"), 18)
|
||
|
||
def test_call_flags_and_timeout_method_args(self):
|
||
"""Test that generated method call functions have @call_flags and
|
||
@timeout_msec args if and only if GLib >= 2.64.
|
||
"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.UsefulInterface">
|
||
<method name="UsefulMethod"/>
|
||
</interface>
|
||
</node>"""
|
||
|
||
# Try without specifying --glib-min-required.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--header"
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GDBusCallFlags call_flags,"), 0)
|
||
self.assertEqual(result.out.strip().count("gint timeout_msec,"), 0)
|
||
|
||
# Specify an old --glib-min-required.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-min-required",
|
||
"2.32",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GDBusCallFlags call_flags,"), 0)
|
||
self.assertEqual(result.out.strip().count("gint timeout_msec,"), 0)
|
||
|
||
# Specify a --glib-min-required ≥ 2.64. The two arguments should be
|
||
# present for both the async and sync method call functions.
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--glib-min-required",
|
||
"2.64",
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(result.out.strip().count("GDBusCallFlags call_flags,"), 2)
|
||
self.assertEqual(result.out.strip().count("gint timeout_msec,"), 2)
|
||
|
||
def test_generate_signal_id_simple_signal(self):
|
||
"""Test that signals IDs are used to emit signals"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.UsefulInterface">
|
||
<signal name="SimpleSignal"/>
|
||
</interface>
|
||
<interface name="org.project.OtherIface">
|
||
<signal name="SimpleSignal"/>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_signal_emit_by_name ("), 0)
|
||
|
||
for iface in ["USEFUL_INTERFACE", "OTHER_IFACE"]:
|
||
enum_name = f"_ORG_PROJECT_{iface}_SIGNALS"
|
||
enum_item = f"_ORG_PROJECT_{iface}_SIMPLE_SIGNAL"
|
||
self.assertIs(stripped_out.count(f"{enum_item},"), 1)
|
||
self.assertIs(stripped_out.count(f"{enum_name}[{enum_item}] ="), 1)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f" g_signal_emit (object, {enum_name}[{enum_item}], 0);"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_generate_signal_id_multiple_signals_types(self):
|
||
"""Test that signals IDs are used to emit signals for all types"""
|
||
|
||
signal_template = "<signal name='{}'><arg name='{}' type='{}'/></signal>"
|
||
generated_signals = [
|
||
signal_template.format(
|
||
f"SingleArgSignal{t.upper()}", f"an_{t}", props.get("variant_type", t)
|
||
)
|
||
for t, props in self.ARGUMENTS_TYPES.items()
|
||
]
|
||
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.SignalingIface">
|
||
<signal name="NoArgSignal" />
|
||
{''.join(generated_signals)}
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_signal_emit_by_name ("), 0)
|
||
|
||
iface = "SIGNALING_IFACE"
|
||
for t in self.ARGUMENTS_TYPES.keys():
|
||
enum_name = f"_ORG_PROJECT_{iface}_SIGNALS"
|
||
enum_item = f"_ORG_PROJECT_{iface}_SINGLE_ARG_SIGNAL_{t.upper()}"
|
||
self.assertIs(stripped_out.count(f"{enum_item},"), 1)
|
||
self.assertIs(stripped_out.count(f"{enum_name}[{enum_item}] ="), 1)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f" g_signal_emit (object, {enum_name}[{enum_item}], 0, arg_an_{t});"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_generate_signal_id_multiple_signal_args_types(self):
|
||
"""Test that signals IDs are used to emit signals for all types"""
|
||
|
||
generated_args = [
|
||
f"<arg name='an_{t}' type='{props.get('variant_type', t)}'/>\n"
|
||
for t, props in self.ARGUMENTS_TYPES.items()
|
||
]
|
||
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.SignalingIface">
|
||
<signal name="SignalWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</signal>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_signal_emit_by_name ("), 0)
|
||
|
||
iface = "SIGNALING_IFACE"
|
||
enum_name = f"_ORG_PROJECT_{iface}_SIGNALS"
|
||
enum_item = f"_ORG_PROJECT_{iface}_SIGNAL_WITH_MANY_ARGS"
|
||
self.assertIs(stripped_out.count(f"{enum_item},"), 1)
|
||
self.assertIs(stripped_out.count(f"{enum_name}[{enum_item}] ="), 1)
|
||
|
||
args = ", ".join([f"arg_an_{t}" for t in self.ARGUMENTS_TYPES.keys()])
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f" g_signal_emit (object, {enum_name}[{enum_item}], 0, {args});"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_generate_signals_marshaller_simple_signal(self):
|
||
"""Test that signals marshaller is generated for simple signal"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.SignalingIface">
|
||
<signal name="SimpleSignal"/>
|
||
</interface>
|
||
<interface name="org.project.OtherSignalingIface">
|
||
<signal name="SimpleSignal"/>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = "org_project_signaling_iface_signal_marshal_simple_signal"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_VOID__VOID (closure"), 2)
|
||
|
||
func_name = "org_project_other_signaling_iface_signal_marshal_simple_signal"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_VOID__VOID (closure"), 2)
|
||
|
||
def test_generate_signals_marshaller_single_typed_args(self):
|
||
"""Test that signals marshaller is generated for each known type"""
|
||
for t, props in self.ARGUMENTS_TYPES.items():
|
||
camel_type = t[0].upper() + t[1:]
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.SignalingIface">
|
||
<signal name="SimpleSignal"/>
|
||
<signal name="SingleArgSignal{camel_type}">
|
||
<arg name="arg_{t}" type="{props.get("variant_type", t)}"/>
|
||
</signal>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--body"
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertEqual(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
self.assertIs(
|
||
stripped_out.count("g_cclosure_marshal_VOID__VOID (closure"), 1
|
||
)
|
||
|
||
func_name = (
|
||
f"org_project_signaling_iface_signal_marshal_single_arg_signal_{t}"
|
||
)
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
if props.get("lacks_marshaller", False):
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f"g_marshal_value_peek_{props['value_type']} (param_values + 1)"
|
||
),
|
||
1,
|
||
)
|
||
else:
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f"g_cclosure_marshal_VOID__{props['value_type'].upper()} (closure"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_generate_signals_marshallers_multiple_args(self):
|
||
"""Test that signals marshallers are generated"""
|
||
generated_args = [
|
||
f"<arg name='an_{t}' type='{props.get('variant_type', t)}'/>\n"
|
||
for t, props in self.ARGUMENTS_TYPES.items()
|
||
]
|
||
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.SignalingIface">
|
||
<signal name="SimpleSignal"/>
|
||
<signal name="SignalWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</signal>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = f"org_project_signaling_iface_signal_marshal_simple_signal"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
func_name = f"org_project_signaling_iface_signal_marshal_signal_with_many_args"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
# Check access to MultipleArgsSignal arguments
|
||
index = 1
|
||
for props in self.ARGUMENTS_TYPES.values():
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f"g_marshal_value_peek_{props['value_type']} (param_values + {index})"
|
||
),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
def test_generate_methods_marshaller_simple_method(self):
|
||
"""Test that methods marshaller is generated for simple method"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.CallableIface">
|
||
<method name="SimpleMethod"/>
|
||
</interface>
|
||
<interface name="org.project.OtherCallableIface">
|
||
<method name="SimpleMethod"/>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = "org_project_callable_iface_method_marshal_simple_method"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
func_name = "org_project_other_callable_iface_method_marshal_simple_method"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
self.assertIs(
|
||
stripped_out.count("g_marshal_value_peek_object (param_values + 1)"), 1
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
"_g_dbus_codegen_marshal_BOOLEAN__OBJECT (\n GClosure"
|
||
),
|
||
1,
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count("_g_dbus_codegen_marshal_BOOLEAN__OBJECT (closure"), 2
|
||
)
|
||
|
||
def test_generate_methods_marshaller_single_typed_in_args(self):
|
||
"""Test that methods marshallers are generated for each known type"""
|
||
for t, props in self.ARGUMENTS_TYPES.items():
|
||
camel_type = t[0].upper() + t[1:]
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.UsefulInterface">
|
||
<method name="SingleArgMethod{camel_type}">
|
||
<arg name="arg_{t}" type="{props.get("variant_type", t)}"/>
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--body"
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertEqual(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = (
|
||
f"org_project_useful_interface_method_marshal_single_arg_method_{t}"
|
||
)
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
self.assertIs(
|
||
stripped_out.count("g_marshal_value_peek_object (param_values + 1)"), 1
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f"g_marshal_value_peek_{props['value_type']} (param_values + 2)"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_generate_methods_marshaller_single_typed_out_args(self):
|
||
"""Test that methods marshallers are generated for each known type"""
|
||
for t, props in self.ARGUMENTS_TYPES.items():
|
||
camel_type = t[0].upper() + t[1:]
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.UsefulInterface">
|
||
<method name="SingleArgMethod{camel_type}">
|
||
<arg name="arg_{t}" type="{props.get("variant_type", t)}" direction="out"/>
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml, "--output", "-", "--body"
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertEqual(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = (
|
||
f"org_project_useful_interface_method_marshal_single_arg_method_{t}"
|
||
)
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
self.assertIs(
|
||
stripped_out.count("g_marshal_value_peek_object (param_values + 1)"), 1
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
self.assertIs(stripped_out.count("(param_values + 2)"), 0)
|
||
|
||
def test_generate_methods_marshallers_multiple_in_args(self):
|
||
"""Test that methods marshallers are generated"""
|
||
generated_args = [
|
||
f"<arg name='an_{t}' type='{props.get('variant_type', t)}'/>\n"
|
||
for t, props in self.ARGUMENTS_TYPES.items()
|
||
]
|
||
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.CallableIface">
|
||
<method name="MethodWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</method>
|
||
<method name="SameMethodWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</method>
|
||
</interface>
|
||
<interface name="org.project.OtherCallableIface">
|
||
<method name="MethodWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = f"org_project_callable_iface_method_marshal_method_with_many_args"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
# Check access to MultipleArgsMethod arguments
|
||
index = 1
|
||
self.assertIs(
|
||
stripped_out.count(f"g_marshal_value_peek_object (param_values + {index})"),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
for props in self.ARGUMENTS_TYPES.values():
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
f"g_marshal_value_peek_{props['value_type']} (param_values + {index})"
|
||
),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
func_types = "_".join(
|
||
[p["value_type"].upper() for p in self.ARGUMENTS_TYPES.values()]
|
||
)
|
||
func_name = f"_g_dbus_codegen_marshal_BOOLEAN__OBJECT_{func_types}"
|
||
self.assertIs(stripped_out.count(f"{func_name} (\n GClosure"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} (closure"), 3)
|
||
|
||
func_name = (
|
||
f"org_project_other_callable_iface_method_marshal_method_with_many_args"
|
||
)
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
def test_generate_methods_marshallers_multiple_out_args(self):
|
||
"""Test that methods marshallers are generated"""
|
||
generated_args = [
|
||
f"<arg name='an_{t}' type='{props.get('variant_type', t)}' direction='out'/>\n"
|
||
for t, props in self.ARGUMENTS_TYPES.items()
|
||
]
|
||
|
||
interface_xml = f"""
|
||
<node>
|
||
<interface name="org.project.CallableIface">
|
||
<method name="MethodWithManyArgs">
|
||
{''.join(generated_args)}
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = f"org_project_callable_iface_method_marshal_method_with_many_args"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
# Check access to MultipleArgsMethod arguments
|
||
index = 1
|
||
self.assertIs(
|
||
stripped_out.count(f"g_marshal_value_peek_object (param_values + {index})"),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
for index in range(index, len(self.ARGUMENTS_TYPES)):
|
||
self.assertIs(stripped_out.count(f"(param_values + {index})"), 0)
|
||
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
|
||
self.assertIs(
|
||
stripped_out.count("_g_dbus_codegen_marshal_BOOLEAN__OBJECT (closure"),
|
||
1,
|
||
)
|
||
|
||
def test_generate_methods_marshallers_with_unix_fds(self):
|
||
"""Test an interface with `h` arguments"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="test.FDPassing">
|
||
<method name="HelloFD">
|
||
<annotation name="org.gtk.GDBus.C.UnixFD" value="1"/>
|
||
<arg name="greeting" direction="in" type="s"/>
|
||
<arg name="response" direction="out" type="s"/>
|
||
</method>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(interface_xml, "--output", "-", "--body")
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("g_cclosure_marshal_generic"), 0)
|
||
|
||
func_name = f"test_fdpassing_method_marshal_hello_fd"
|
||
self.assertIs(stripped_out.count(f"{func_name},"), 1)
|
||
self.assertIs(stripped_out.count(f"{func_name} ("), 1)
|
||
|
||
self.assertLess(
|
||
stripped_out.index("arg_method_invocation"),
|
||
stripped_out.index("arg_fd_list"),
|
||
)
|
||
|
||
index = 1
|
||
self.assertIs(
|
||
stripped_out.count(f"g_marshal_value_peek_object (param_values + {index})"),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
self.assertIs(
|
||
stripped_out.count(f"g_marshal_value_peek_object (param_values + {index})"),
|
||
1,
|
||
)
|
||
|
||
index += 1
|
||
self.assertIs(
|
||
stripped_out.count(f"g_marshal_value_peek_string (param_values + {index})"),
|
||
1,
|
||
)
|
||
index += 1
|
||
|
||
self.assertIs(
|
||
stripped_out.count("g_value_set_boolean (return_value, v_return);"), 1
|
||
)
|
||
|
||
def test_generate_valid_docbook(self):
|
||
"""Test the basic functionality of the docbook generator."""
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<!-- Resize:
|
||
@size: New partition size in bytes, 0 for maximal size.
|
||
@options: Options.
|
||
@since 2.7.2
|
||
|
||
Resizes the partition.
|
||
|
||
The partition will not change its position but might be slightly bigger
|
||
than requested due to sector counts and alignment (e.g. 1MiB).
|
||
If the requested size can't be allocated it results in an error.
|
||
The maximal size can automatically be set by using 0 as size.
|
||
-->
|
||
<method name="Resize">
|
||
<arg name="size" direction="in" type="t"/>
|
||
<arg name="options" direction="in" type="a{sv}"/>
|
||
</method>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents,
|
||
"--generate-docbook",
|
||
"test",
|
||
)
|
||
self.assertEqual("", res.err)
|
||
self.assertEqual("", res.out)
|
||
with open("test-org.project.Bar.Frobnicator.xml", "r") as f:
|
||
self.assertIsNotNone(ET.parse(f))
|
||
|
||
def test_indentation_preservation_in_comments(self):
|
||
"""Test if the parser preserves relative indentation in XML comments"""
|
||
markup_list = """\
|
||
- The mnemonic key activates the object if it is presently enabled onscreen.
|
||
This typically corresponds to the underlined letter within the widget.
|
||
Example: "n" in a traditional "New..." menu item or the "a" in "Apply" for
|
||
a button."""
|
||
|
||
xml_contents = """
|
||
<node>
|
||
<interface name="org.project.Bar.Frobnicator">
|
||
<!-- GetKeyBinding:
|
||
@index: 0-based index of the action to query.
|
||
|
||
Gets the keybinding which can be used to activate this action, if one
|
||
exists. The string returned should contain localized, human-readable,
|
||
key sequences as they would appear when displayed on screen. It must
|
||
be in the format "mnemonic;sequence;shortcut".
|
||
|
||
- The mnemonic key activates the object if it is presently enabled onscreen.
|
||
This typically corresponds to the underlined letter within the widget.
|
||
Example: "n" in a traditional "New..." menu item or the "a" in "Apply" for
|
||
a button.
|
||
|
||
If there is no key binding for this action, return "".
|
||
-->
|
||
<method name="GetKeyBinding">
|
||
<arg type="i" name="index" direction="in"/>
|
||
<arg type="s" direction="out"/>
|
||
</method>
|
||
</interface>
|
||
</node>
|
||
"""
|
||
for format, ext in [("rst", "rst"), ("md", "md"), ("docbook", "xml")]:
|
||
res = self.runCodegenWithInterface(
|
||
xml_contents, f"--generate-{format}", "test"
|
||
)
|
||
self.assertFalse(res.err)
|
||
self.assertFalse(res.out)
|
||
with open(f"test-org.project.Bar.Frobnicator.{ext}", "r") as f:
|
||
self.assertIn(markup_list, f.read())
|
||
|
||
def test_extension_help(self):
|
||
"""Test the --help for the extension"""
|
||
result = self.runCodegen("--help")
|
||
self.assertIn("(unstable API)", result.out)
|
||
|
||
def test_extension_bad_file(self):
|
||
"""Test the --help for the extension"""
|
||
result = self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--extension-path",
|
||
"/path/that/does/not/exist",
|
||
should_fail=True,
|
||
)
|
||
self.assertIn(
|
||
"ERROR: Loading extension ‘/path/that/does/not/exist’ failed", result.err
|
||
)
|
||
self.assertEqual("", result.out)
|
||
|
||
def test_extension_empty_interface_header(self):
|
||
"""Test generating a header with an empty interface file with the test
|
||
extension.
|
||
"""
|
||
result = self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
#ifndef __STDOUT__
|
||
#define __STDOUT__
|
||
|
||
#include <gio/gio.h>
|
||
/* codegen-test-extension include */
|
||
|
||
G_BEGIN_DECLS
|
||
|
||
|
||
G_END_DECLS
|
||
|
||
#endif /* __STDOUT__ */""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_extension_empty_interface_body(self):
|
||
"""Test generating a body with an empty interface file with the test
|
||
extension.
|
||
"""
|
||
result = self.runCodegenWithInterface(
|
||
"",
|
||
"--output",
|
||
"-",
|
||
"--body",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
self.assertEqual("", result.err)
|
||
self.assertEqual(
|
||
"""{standard_top_comment}
|
||
|
||
{standard_config_h_include}
|
||
|
||
{standard_header_includes}
|
||
|
||
{private_gvalues_getters}
|
||
|
||
{standard_typedefs_and_helpers}
|
||
|
||
/* codegen-test-extension body preamble */""".format(
|
||
**result.subs
|
||
),
|
||
result.out.strip(),
|
||
)
|
||
|
||
def test_extension_header_declare_types(self):
|
||
"""Test generating header declarations from the test extension"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.Foo">
|
||
</interface>
|
||
<interface name="org.project.Bar">
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
"/* codegen-test-extension declare type for iface org.project.Foo */"
|
||
),
|
||
1,
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
"/* codegen-test-extension declare type for iface org.project.Bar */"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_extension_code_generate(self):
|
||
"""Test code generation from the test extension"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.Foo">
|
||
</interface>
|
||
<interface name="org.project.Bar">
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--body",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
"/* codegen-test-extension generate for iface org.project.Foo */"
|
||
),
|
||
1,
|
||
)
|
||
self.assertIs(
|
||
stripped_out.count(
|
||
"/* codegen-test-extension generate for iface org.project.Bar */"
|
||
),
|
||
1,
|
||
)
|
||
|
||
def test_extension_code_generate(self):
|
||
"""Test changing the GDBusInterfaceSkeleton subtype from the test
|
||
extension that skeletons derive from
|
||
"""
|
||
interface_xml = """
|
||
<node>
|
||
<interface name="org.project.UsefulInterface">
|
||
<method name="UsefulMethod"/>
|
||
</interface>
|
||
</node>"""
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--body",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("G_TYPE_DBUS_INTERFACE_SKELETON"), 0)
|
||
self.assertIs(stripped_out.count("NEW_TYPE_DBUS_INTERFACE_SKELETON"), 2)
|
||
|
||
result = self.runCodegenWithInterface(
|
||
interface_xml,
|
||
"--output",
|
||
"-",
|
||
"--header",
|
||
"--extension-path",
|
||
self.get_codegen_ext_path(),
|
||
)
|
||
stripped_out = result.out.strip()
|
||
self.assertFalse(result.err)
|
||
self.assertIs(stripped_out.count("GDBusInterfaceSkeleton"), 0)
|
||
self.assertIs(stripped_out.count("NewDBusInterfaceSkeleton"), 2)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main(testRunner=taptestrunner.TAPTestRunner())
|