diff --git a/gobject/glib-genmarshal.in b/gobject/glib-genmarshal.in index dc4c7eacc..1ea2ad9e8 100755 --- a/gobject/glib-genmarshal.in +++ b/gobject/glib-genmarshal.in @@ -330,6 +330,7 @@ IN_ARGS = { 'ctype': 'gpointer', 'getter': 'g_marshal_value_peek_string', 'box': ['g_strdup', 'g_free'], + 'static-check': True, }, 'PARAM': { 'signal': 'PARAM', @@ -360,7 +361,7 @@ IN_ARGS = { 'signal': 'VARIANT', 'ctype': 'gpointer', 'getter': 'g_marshal_value_peek_variant', - 'box': ['g_variant_ref', 'g_variant_unref'], + 'box': ['g_variant_ref_sink', 'g_variant_unref'], 'static-check': True, 'takes-type': False, }, diff --git a/gobject/tests/genmarshal.py b/gobject/tests/genmarshal.py new file mode 100644 index 000000000..0da61f3a2 --- /dev/null +++ b/gobject/tests/genmarshal.py @@ -0,0 +1,599 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# Copyright © 2019 Endless Mobile, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +"""Integration tests for glib-genmarshal utility.""" + +import collections +import os +import shutil +import subprocess +import tempfile +from textwrap import dedent +import unittest + +import taptestrunner + + +Result = collections.namedtuple('Result', ('info', 'out', 'err', 'subs')) + + +class TestGenmarshal(unittest.TestCase): + """Integration test for running glib-genmarshal. + + This can be run when installed or uninstalled. When uninstalled, it + requires G_TEST_BUILDDIR and G_TEST_SRCDIR to be set. + + The idea with this test harness is to test the glib-genmarshal utility, its + handling of command line arguments, its exit statuses, and its handling of + various marshaller lists. In future we could split the core glib-genmarshal + parsing and generation code out into a library and unit test that, and + convert this test to just check command line behaviour. + """ + def setUp(self): + self.timeout_seconds = 10 # seconds per test + self.tmpdir = tempfile.TemporaryDirectory() + os.chdir(self.tmpdir.name) + print('tmpdir:', self.tmpdir.name) + if 'G_TEST_BUILDDIR' in os.environ: + self.__genmarshal = \ + os.path.join(os.environ['G_TEST_BUILDDIR'], '..', + 'glib-genmarshal') + else: + self.__genmarshal = shutil.which('glib-genmarshal') + print('genmarshal:', self.__genmarshal) + + def tearDown(self): + self.tmpdir.cleanup() + + def runGenmarshal(self, *args): + argv = [self.__genmarshal] + argv.extend(args) + print('Running:', argv) + + env = os.environ.copy() + env['LC_ALL'] = 'C.UTF-8' + print('Environment:', env) + + info = subprocess.run(argv, timeout=self.timeout_seconds, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + info.check_returncode() + out = info.stdout.decode('utf-8').strip() + err = info.stderr.decode('utf-8').strip() + + # Known substitutions for standard boilerplate + subs = { + 'standard_top_comment': + 'This file is generated by glib-genmarshal, do not modify ' + 'it. This code is licensed under the same license as the ' + 'containing project. Note that it links to GLib, so must ' + 'comply with the LGPL linking clauses.', + 'standard_top_pragma': dedent( + ''' + #ifndef __G_CCLOSURE_USER_MARSHAL_MARSHAL_H__ + #define __G_CCLOSURE_USER_MARSHAL_MARSHAL_H__ + ''').strip(), + 'standard_bottom_pragma': dedent( + ''' + #endif /* __G_CCLOSURE_USER_MARSHAL_MARSHAL_H__ */ + ''').strip(), + 'standard_includes': dedent( + ''' + #include + ''').strip(), + 'standard_marshal_peek_defines': dedent( + ''' + #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 */ + ''').strip(), + } + + result = Result(info, out, err, subs) + + print('Output:', result.out) + return result + + def runGenmarshalWithList(self, list_contents, *args): + with tempfile.NamedTemporaryFile(dir=self.tmpdir.name, + suffix='.list') as list_file: + # Write out the list. + list_file.write(list_contents.encode('utf-8')) + print(list_file.name + ':', list_contents) + list_file.flush() + + header_result = self.runGenmarshal(list_file.name, + '--header', *args) + body_result = self.runGenmarshal(list_file.name, + '--body', *args) + + header_result.subs['list_path'] = list_file.name + body_result.subs['list_path'] = list_file.name + + return (header_result, body_result) + + def test_help(self): + """Test the --help argument.""" + result = self.runGenmarshal('--help') + self.assertIn('usage: glib-genmarshal', result.out) + + def test_no_args(self): + """Test running with no arguments at all.""" + result = self.runGenmarshal() + self.assertEqual('', result.err) + self.assertEqual('', result.out) + + def test_empty_list(self): + """Test running with an empty list.""" + (header_result, body_result) = \ + self.runGenmarshalWithList('', '--quiet') + + self.assertEqual('', header_result.err) + self.assertEqual('', body_result.err) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_top_pragma} + + {standard_includes} + + G_BEGIN_DECLS + + + G_END_DECLS + + {standard_bottom_pragma} + ''').strip().format(**header_result.subs), + header_result.out.strip()) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_includes} + + {standard_marshal_peek_defines} + ''').strip().format(**body_result.subs), + body_result.out.strip()) + + def test_void_boolean(self): + """Test running with a basic VOID:BOOLEAN list.""" + (header_result, body_result) = \ + self.runGenmarshalWithList('VOID:BOOLEAN', '--quiet') + + self.assertEqual('', header_result.err) + self.assertEqual('', body_result.err) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_top_pragma} + + {standard_includes} + + G_BEGIN_DECLS + + /* VOID:BOOLEAN ({list_path}:1) */ + #define g_cclosure_user_marshal_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN + + + G_END_DECLS + + {standard_bottom_pragma} + ''').strip().format(**header_result.subs), + header_result.out.strip()) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_includes} + + {standard_marshal_peek_defines} + ''').strip().format(**body_result.subs), + body_result.out.strip()) + + def test_void_boolean_int64(self): + """Test running with a non-trivial VOID:BOOLEAN,INT64 list.""" + (header_result, body_result) = \ + self.runGenmarshalWithList('VOID:BOOLEAN,INT64', '--quiet') + + self.assertEqual('', header_result.err) + self.assertEqual('', body_result.err) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_top_pragma} + + {standard_includes} + + G_BEGIN_DECLS + + /* VOID:BOOLEAN,INT64 ({list_path}:1) */ + extern + void g_cclosure_user_marshal_VOID__BOOLEAN_INT64 (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + + + G_END_DECLS + + {standard_bottom_pragma} + ''').strip().format(**header_result.subs), + header_result.out.strip()) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_includes} + + {standard_marshal_peek_defines} + + /* VOID:BOOLEAN,INT64 ({list_path}:1) */ + void + g_cclosure_user_marshal_VOID__BOOLEAN_INT64 (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) + {{ + typedef void (*GMarshalFunc_VOID__BOOLEAN_INT64) (gpointer data1, + gboolean arg1, + gint64 arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__BOOLEAN_INT64 callback; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + {{ + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + }} + else + {{ + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + }} + callback = (GMarshalFunc_VOID__BOOLEAN_INT64) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boolean (param_values + 1), + g_marshal_value_peek_int64 (param_values + 2), + data2); + }} + ''').strip().format(**body_result.subs), + body_result.out.strip()) + + def test_void_variant_nostdinc_valist_marshaller(self): + """Test running with a basic VOID:VARIANT list, but without the + standard marshallers, and with valist support enabled. This checks that + the valist marshaller for VARIANT correctly sinks floating variants. + + See issue #1793. + """ + (header_result, body_result) = \ + self.runGenmarshalWithList('VOID:VARIANT', '--quiet', '--nostdinc', + '--valist-marshaller') + + self.assertEqual('', header_result.err) + self.assertEqual('', body_result.err) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_top_pragma} + + G_BEGIN_DECLS + + /* VOID:VARIANT ({list_path}:1) */ + extern + void g_cclosure_user_marshal_VOID__VARIANT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + extern + void g_cclosure_user_marshal_VOID__VARIANTv (GClosure *closure, + GValue *return_value, + gpointer instance, + va_list args, + gpointer marshal_data, + int n_params, + GType *param_types); + + + G_END_DECLS + + {standard_bottom_pragma} + ''').strip().format(**header_result.subs), + header_result.out.strip()) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_marshal_peek_defines} + + /* VOID:VARIANT ({list_path}:1) */ + void + g_cclosure_user_marshal_VOID__VARIANT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) + {{ + typedef void (*GMarshalFunc_VOID__VARIANT) (gpointer data1, + gpointer arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__VARIANT callback; + + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + {{ + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + }} + else + {{ + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + }} + callback = (GMarshalFunc_VOID__VARIANT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_variant (param_values + 1), + data2); + }} + + void + g_cclosure_user_marshal_VOID__VARIANTv (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + gpointer instance, + va_list args, + gpointer marshal_data, + int n_params, + GType *param_types) + {{ + typedef void (*GMarshalFunc_VOID__VARIANT) (gpointer data1, + gpointer arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__VARIANT callback; + gpointer arg0; + va_list args_copy; + + G_VA_COPY (args_copy, args); + arg0 = (gpointer) va_arg (args_copy, gpointer); + if ((param_types[0] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && arg0 != NULL) + arg0 = g_variant_ref_sink (arg0); + va_end (args_copy); + + + if (G_CCLOSURE_SWAP_DATA (closure)) + {{ + data1 = closure->data; + data2 = instance; + }} + else + {{ + data1 = instance; + data2 = closure->data; + }} + callback = (GMarshalFunc_VOID__VARIANT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + arg0, + data2); + if ((param_types[0] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && arg0 != NULL) + g_variant_unref (arg0); + }} + ''').strip().format(**body_result.subs), + body_result.out.strip()) + + def test_void_string_nostdinc(self): + """Test running with a basic VOID:STRING list, but without the + standard marshallers, and with valist support enabled. This checks that + the valist marshaller for STRING correctly skips a string copy if the + argument is static. + + See issue #1792. + """ + (header_result, body_result) = \ + self.runGenmarshalWithList('VOID:STRING', '--quiet', '--nostdinc', + '--valist-marshaller') + + self.assertEqual('', header_result.err) + self.assertEqual('', body_result.err) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_top_pragma} + + G_BEGIN_DECLS + + /* VOID:STRING ({list_path}:1) */ + extern + void g_cclosure_user_marshal_VOID__STRING (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + extern + void g_cclosure_user_marshal_VOID__STRINGv (GClosure *closure, + GValue *return_value, + gpointer instance, + va_list args, + gpointer marshal_data, + int n_params, + GType *param_types); + + + G_END_DECLS + + {standard_bottom_pragma} + ''').strip().format(**header_result.subs), + header_result.out.strip()) + + self.assertEqual(dedent( + ''' + /* {standard_top_comment} */ + {standard_marshal_peek_defines} + + /* VOID:STRING ({list_path}:1) */ + void + g_cclosure_user_marshal_VOID__STRING (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) + {{ + typedef void (*GMarshalFunc_VOID__STRING) (gpointer data1, + gpointer arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__STRING callback; + + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) + {{ + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + }} + else + {{ + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + }} + callback = (GMarshalFunc_VOID__STRING) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_string (param_values + 1), + data2); + }} + + void + g_cclosure_user_marshal_VOID__STRINGv (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + gpointer instance, + va_list args, + gpointer marshal_data, + int n_params, + GType *param_types) + {{ + typedef void (*GMarshalFunc_VOID__STRING) (gpointer data1, + gpointer arg1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_VOID__STRING callback; + gpointer arg0; + va_list args_copy; + + G_VA_COPY (args_copy, args); + arg0 = (gpointer) va_arg (args_copy, gpointer); + if ((param_types[0] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && arg0 != NULL) + arg0 = g_strdup (arg0); + va_end (args_copy); + + + if (G_CCLOSURE_SWAP_DATA (closure)) + {{ + data1 = closure->data; + data2 = instance; + }} + else + {{ + data1 = instance; + data2 = closure->data; + }} + callback = (GMarshalFunc_VOID__STRING) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + arg0, + data2); + if ((param_types[0] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && arg0 != NULL) + g_free (arg0); + }} + ''').strip().format(**body_result.subs), + body_result.out.strip()) + + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build index 7c3e5cd9d..9d5aac849 100644 --- a/gobject/tests/meson.build +++ b/gobject/tests/meson.build @@ -56,6 +56,11 @@ if cc.get_id() != 'msvc' gobject_tests += {'autoptr' : {}} endif +python_tests = [ + 'genmarshal.py', + 'mkenums.py', +] + # FIXME: put common bits of test environment() in one location # Not entirely random of course, but at least it changes over time random_number = minor_version + meson.version().split('.').get(1).to_int() @@ -106,10 +111,39 @@ foreach test_name, extra_args : gobject_tests test(test_name, exe, env : test_env, timeout : timeout, suite : suite) endforeach -test( - 'mkenums.py', - python, - args: files('mkenums.py'), - env: test_env, - suite: ['gobject'], -) +foreach test_name : python_tests + test( + test_name, + python, + args: files(test_name), + env: test_env, + suite: ['gobject'], + ) + + if installed_tests_enabled + install_data( + files(test_name), + install_dir: installed_tests_execdir, + install_mode: 'rwxr-xr-x', + ) + + test_conf = configuration_data() + test_conf.set('installed_tests_dir', installed_tests_execdir) + test_conf.set('program', test_name) + test_conf.set('env', '') + configure_file( + input: installed_tests_template_tap, + output: test_name + '.test', + install_dir: installed_tests_metadir, + configuration: test_conf, + ) + endif +endforeach + +# TAP test runner for Python tests +if installed_tests_enabled + install_data( + files('taptestrunner.py'), + install_dir: installed_tests_execdir, + ) +endif diff --git a/gobject/tests/mkenums.py b/gobject/tests/mkenums.py index f2a1ad95c..9b4632497 100644 --- a/gobject/tests/mkenums.py +++ b/gobject/tests/mkenums.py @@ -22,6 +22,7 @@ import collections import os +import shutil import subprocess import tempfile import textwrap @@ -57,7 +58,7 @@ class TestMkenums(unittest.TestCase): os.path.join(os.environ['G_TEST_BUILDDIR'], '..', 'glib-mkenums') else: - self.__mkenums = os.path.join('/', 'usr', 'bin', 'glib-mkenums') + self.__mkenums = shutil.which('glib-mkenums') print('rspfile: {}, mkenums:'.format(self.rspfile), self.__mkenums) def tearDown(self):