mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 00:12:19 +01:00 
			
		
		
		
	GUnixFDList actually comes *after* the GDBusMethodInvocation, but this was mistakenly putting it first. Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
		
			
				
	
	
		
			1462 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1462 lines
		
	
	
		
			55 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):
 | |
|         return self.runTestProgram(args)
 | |
| 
 | |
|     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):
 | |
|         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)
 | |
| 
 | |
|         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())
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     unittest.main(testRunner=taptestrunner.TAPTestRunner())
 |