#!/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 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")) def on_win32(): return sys.platform.find("win") != -1 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 \n" "#ifdef G_OS_UNIX\n" "# include \n" "#endif", "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() @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_empty_interface_header(self): """Test generating a header with an empty interface file.""" result = self.runCodegenWithInterface("", "--output", "/dev/stdout", "--header") self.assertEqual("", result.err) self.assertEqual( """{standard_top_comment} #ifndef __STDOUT__ #define __STDOUT__ #include G_BEGIN_DECLS G_END_DECLS #endif /* __STDOUT__ */""".format( **result.subs ), result.out.strip(), ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_empty_interface_body(self): """Test generating a body with an empty interface file.""" result = self.runCodegenWithInterface("", "--output", "/dev/stdout", "--body") self.assertEqual("", result.err) self.assertEqual( """{standard_top_comment} {standard_config_h_include} #include "stdout.h" {standard_header_includes} {private_gvalues_getters} {standard_typedefs_and_helpers}""".format( **result.subs ), result.out.strip(), ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_reproducible(self): """Test builds are reproducible regardless of file ordering.""" xml_contents1 = """ """ xml_contents2 = """ """ 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", "/dev/stdout", header_or_body, ) self.assertEqual("", result1.err) result2 = self.runCodegen( xml_file2.name, xml_file1.name, "--output", "/dev/stdout", 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 = """ """ 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.assertTrue(len(xml_data) != 0) def test_generate_md(self): """Test the basic functionality of the markdown generator.""" xml_contents = """ """ 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.assertTrue(len(rst) != 0) def test_generate_rst(self): """Test the basic functionality of the rst generator.""" xml_contents = """ """ 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.assertTrue(len(rst) != 0) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_invalid(self): """Test running with an invalid --glib-min-required.""" with self.assertRaises(subprocess.CalledProcessError): self.runCodegenWithInterface( "", "--output", "/dev/stdout", "--body", "--glib-min-required", "hello mum", ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--body", "--glib-min-required", "2.6" ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--header", "--glib-min-required", "3", "--glib-max-allowed", "3.2", ) self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_with_micro(self): """Test running with a --glib-min-required which contains a micro version.""" result = self.runCodegenWithInterface( "", "--output", "/dev/stdout", "--header", "--glib-min-required", "2.46.2" ) self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--body", "--glib-max-allowed", "2.6" ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--header", "--glib-max-allowed", "3" ) self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_with_micro(self): """Test running with a --glib-max-allowed which contains a micro version.""" result = self.runCodegenWithInterface( "", "--output", "/dev/stdout", "--header", "--glib-max-allowed", "2.46.2" ) self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--header", "--glib-max-allowed", "2.63", "--glib-min-required", "2.64", ) self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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", "/dev/stdout", "--body", "--glib-max-allowed", "2.62", "--glib-min-required", "2.64", ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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""" """ with self.assertRaises(subprocess.CalledProcessError): self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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""" """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--body" ) self.assertEqual("", result.err) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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 = """ """ # Try without specifying --glib-min-required. result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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", "/dev/stdout", "--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", "/dev/stdout", "--header", "--glib-min-required", "2.64", ) self.assertEqual("", result.err) self.assertEqual(result.out.strip().count("GUnixFDList"), 18) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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 = """ """ # Try without specifying --glib-min-required. result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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", "/dev/stdout", "--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", "/dev/stdout", "--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) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_signal_id_simple_signal(self): """Test that signals IDs are used to emit signals""" interface_xml = """ """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_signal_id_multiple_signals_types(self): """Test that signals IDs are used to emit signals for all types""" signal_template = "" 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""" {''.join(generated_signals)} """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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"\n" for t, props in self.ARGUMENTS_TYPES.items() ] interface_xml = f""" {''.join(generated_args)} """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_signals_marshaller_simple_signal(self): """Test that signals marshaller is generated for simple signal""" interface_xml = """ """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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""" """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_signals_marshallers_multiple_args(self): """Test that signals marshallers are generated""" generated_args = [ f"\n" for t, props in self.ARGUMENTS_TYPES.items() ] interface_xml = f""" {''.join(generated_args)} """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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 @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_methods_marshaller_simple_method(self): """Test that methods marshaller is generated for simple method""" interface_xml = """ """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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 ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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""" """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") 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""" """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_methods_marshallers_multiple_in_args(self): """Test that methods marshallers are generated""" generated_args = [ f"\n" for t, props in self.ARGUMENTS_TYPES.items() ] interface_xml = f""" {''.join(generated_args)} {''.join(generated_args)} {''.join(generated_args)} """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_methods_marshallers_multiple_out_args(self): """Test that methods marshallers are generated""" generated_args = [ f"\n" for t, props in self.ARGUMENTS_TYPES.items() ] interface_xml = f""" {''.join(generated_args)} """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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, ) @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_generate_methods_marshallers_with_unix_fds(self): """Test an interface with `h` arguments""" interface_xml = """ """ result = self.runCodegenWithInterface( interface_xml, "--output", "/dev/stdout", "--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 = """ """ 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.assertTrue(ET.parse(f) is not None) 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 = """ """ 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())