| #!/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 collections | |
| import os | |
| import shutil | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import textwrap | |
| import unittest | |
| import xml.etree.ElementTree as ET | |
| import taptestrunner | |
| # Disable line length warnings as wrapping the C code templates would be hard | |
| # flake8: noqa: E501 | |
| Result = collections.namedtuple("Result", ("info", "out", "err", "subs")) | |
| class TestCodegen(unittest.TestCase): | |
| """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. | |
| """ | |
| # Track the cwd, we want to back out to that to clean up our tempdir | |
| cwd = "" | |
| 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 setUp(self): | |
| self.timeout_seconds = 6 # seconds per test | |
| self.tmpdir = tempfile.TemporaryDirectory() | |
| self.cwd = os.getcwd() | |
| os.chdir(self.tmpdir.name) | |
| print("tmpdir:", self.tmpdir.name) | |
| if "G_TEST_BUILDDIR" in os.environ: | |
| self.__codegen = os.path.join( | |
| os.environ["G_TEST_BUILDDIR"], | |
| "..", | |
| "gdbus-2.0", | |
| "codegen", | |
| "gdbus-codegen", | |
| ) | |
| else: | |
| self.__codegen = shutil.which("gdbus-codegen") | |
| print("codegen:", self.__codegen) | |
| def tearDown(self): | |
| os.chdir(self.cwd) | |
| self.tmpdir.cleanup() | |
| def runCodegen(self, *args): | |
| argv = [self.__codegen] | |
| # shebang lines are not supported on native | |
| # Windows consoles | |
| if os.name == "nt": | |
| argv.insert(0, sys.executable) | |
| argv.extend(args) | |
| print("Running:", argv) | |
| env = os.environ.copy() | |
| env["LC_ALL"] = "C.UTF-8" | |
| env["G_DEBUG"] = "fatal-warnings" | |
| print("Environment:", env) | |
| # We want to ensure consistent line endings... | |
| info = subprocess.run( | |
| argv, | |
| timeout=self.timeout_seconds, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| env=env, | |
| universal_newlines=True, | |
| ) | |
| info.check_returncode() | |
| out = info.stdout.strip() | |
| err = info.stderr.strip() | |
| # Known substitutions for standard boilerplate | |
| subs = { | |
| "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" | |
| "}", | |
| } | |
| result = Result(info, out, err, subs) | |
| print("Output:", result.out) | |
| return result | |
| def runCodegenWithInterface(self, interface_contents, *args): | |
| 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) | |
| 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) | |
| 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()) | |
| if __name__ == "__main__": | |
| unittest.main(testRunner=taptestrunner.TAPTestRunner()) |