#!/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()) def test_void_param_nostdinc(self): """Test running with a basic VOID:PARAM list, but without the standard marshallers, and with valist support enabled. This checks that the valist marshaller for PARAM correctly skips a param copy if the argument is static. See issue #1792. """ self.maxDiff = None # TODO (header_result, body_result) = \ self.runGenmarshalWithList('VOID:PARAM', '--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:PARAM ({list_path}:1) */ extern void g_cclosure_user_marshal_VOID__PARAM (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__PARAMv (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:PARAM ({list_path}:1) */ void g_cclosure_user_marshal_VOID__PARAM (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__PARAM) (gpointer data1, gpointer arg1, gpointer data2); GCClosure *cc = (GCClosure *) closure; gpointer data1, data2; GMarshalFunc_VOID__PARAM 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__PARAM) (marshal_data ? marshal_data : cc->callback); callback (data1, g_marshal_value_peek_param (param_values + 1), data2); }} void g_cclosure_user_marshal_VOID__PARAMv (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__PARAM) (gpointer data1, gpointer arg1, gpointer data2); GCClosure *cc = (GCClosure *) closure; gpointer data1, data2; GMarshalFunc_VOID__PARAM 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_param_spec_ref (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__PARAM) (marshal_data ? marshal_data : cc->callback); callback (data1, arg0, data2); if ((param_types[0] & G_SIGNAL_TYPE_STATIC_SCOPE) == 0 && arg0 != NULL) g_param_spec_unref (arg0); }} ''').strip().format(**body_result.subs), body_result.out.strip()) if __name__ == '__main__': unittest.main(testRunner=taptestrunner.TAPTestRunner())