diff --git a/gobject/tests/genmarshal.py b/gobject/tests/genmarshal.py new file mode 100644 index 000000000..d00f180ff --- /dev/null +++ b/gobject/tests/genmarshal.py @@ -0,0 +1,336 @@ +#!/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()) + + +if __name__ == '__main__': + unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build index 0a2e7de03..9d5aac849 100644 --- a/gobject/tests/meson.build +++ b/gobject/tests/meson.build @@ -57,6 +57,7 @@ if cc.get_id() != 'msvc' endif python_tests = [ + 'genmarshal.py', 'mkenums.py', ]