From c60ef6a2bcc05010a89328d5fc2704d1aa505e2a Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sat, 10 Mar 2018 18:04:14 +0100 Subject: [PATCH 1/4] tests_gi: Use capture_output() context manager instead of manually mocking stdout --- tests/helper.py | 2 +- tests/test_gi.py | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/helper.py b/tests/helper.py index c4308fee..892cb740 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -113,7 +113,7 @@ def capture_glib_deprecation_warnings(): @contextlib.contextmanager def capture_output(): """ - with capture_output as (stdout, stderr): + with capture_output() as (stdout, stderr): some_action() print(stdout.getvalue(), stderr.getvalue()) """ diff --git a/tests/test_gi.py b/tests/test_gi.py index d0c72b64..3b77ff2d 100644 --- a/tests/test_gi.py +++ b/tests/test_gi.py @@ -13,7 +13,6 @@ import subprocess import gc import weakref import warnings -from io import StringIO, BytesIO import gi import gi.overrides @@ -24,7 +23,7 @@ from gi.repository import GObject, GLib, Gio from gi.repository import GIMarshallingTests from compathelper import _bytes, _unicode -from helper import capture_exceptions +from helper import capture_exceptions, capture_output if sys.version_info < (3, 0): CONSTANT_UTF8 = "const \xe2\x99\xa5 utf8" @@ -2836,16 +2835,9 @@ class TestModule(unittest.TestCase): self.assertTrue(hasattr(item, '__class__')) def test_help(self): - orig_stdout = sys.stdout - try: - if sys.version_info < (3, 0): - sys.stdout = BytesIO() - else: - sys.stdout = StringIO() + with capture_output() as (stdout, stderr): help(GIMarshallingTests) - output = sys.stdout.getvalue() - finally: - sys.stdout = orig_stdout + output = stdout.getvalue() self.assertTrue('SimpleStruct' in output, output) self.assertTrue('Interface2' in output, output) -- 2.18.0 From 1826e41cd317ba3c19cf8767b1ef8752f1865aac Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sat, 10 Mar 2018 18:54:28 +0100 Subject: [PATCH 2/4] IntrospectionModule: __path__ should be List[str] and not str This fixes a crash when calling help() on the module which got stricter with Python 3.7. It's a bit questionable why the type has __path__ in the first place as that's only meant for packages. But let's leave that for now. --- gi/module.py | 5 +++-- tests/test_gi.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gi/module.py b/gi/module.py index fd8f5080..9e8b5256 100644 --- a/gi/module.py +++ b/gi/module.py @@ -124,10 +124,11 @@ class IntrospectionModule(object): self._version = version self.__name__ = 'gi.repository.' + namespace - self.__path__ = repository.get_typelib_path(self._namespace) + path = repository.get_typelib_path(self._namespace) + self.__path__ = [path] if _have_py3: # get_typelib_path() delivers bytes, not a string - self.__path__ = self.__path__.decode('UTF-8') + self.__path__ = [path.decode('UTF-8')] if self._version is None: self._version = repository.get_version(self._namespace) diff --git a/tests/test_gi.py b/tests/test_gi.py index 3b77ff2d..2fa0423d 100644 --- a/tests/test_gi.py +++ b/tests/test_gi.py @@ -2813,8 +2813,10 @@ class TestKeywords(unittest.TestCase): class TestModule(unittest.TestCase): def test_path(self): - self.assertTrue(GIMarshallingTests.__path__.endswith('GIMarshallingTests-1.0.typelib'), - GIMarshallingTests.__path__) + path = GIMarshallingTests.__path__ + assert isinstance(path, list) + assert len(path) == 1 + assert path[0].endswith('GIMarshallingTests-1.0.typelib') def test_str(self): self.assertTrue("'GIMarshallingTests' from '" in str(GIMarshallingTests), -- 2.18.0 From 8adc8be0a2ab17e5215a4bc6f63a9ee391237596 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sun, 11 Mar 2018 13:13:30 +0100 Subject: [PATCH 3/4] _struct_dealloc: handle being called with an error set With Python 3.7 it gets called with an error set and tp_dealloc implementations need to handle that. Fix by saving and restoring the error. --- gi/pygi-struct.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gi/pygi-struct.c b/gi/pygi-struct.c index 4d5b5411..e2906e0a 100644 --- a/gi/pygi-struct.c +++ b/gi/pygi-struct.c @@ -61,7 +61,14 @@ out: static void _struct_dealloc (PyGIStruct *self) { - GIBaseInfo *info = _struct_get_info ( (PyObject *) self ); + GIBaseInfo *info; + PyObject *error_type, *error_value, *error_traceback; + gboolean have_error = !!PyErr_Occurred (); + + if (have_error) + PyErr_Fetch (&error_type, &error_value, &error_traceback); + + info = _struct_get_info ( (PyObject *) self ); if (info != NULL && g_struct_info_is_foreign ( (GIStructInfo *) info)) { pygi_struct_foreign_release (info, pyg_pointer_get_ptr (self)); @@ -73,6 +80,9 @@ _struct_dealloc (PyGIStruct *self) g_base_info_unref (info); } + if (have_error) + PyErr_Restore (error_type, error_value, error_traceback); + Py_TYPE (self)->tp_free ((PyObject *)self); } -- 2.18.0 From 2299d46e7a57a74f734844ae28bfd536e89e5254 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sat, 10 Mar 2018 20:03:33 +0100 Subject: [PATCH 4/4] marshal-cleanup: save and restore exception around cleanup With Python 3.7 some Python API in the cleanup path clears exceptions which makes us return NULL in the end without an error set. Make if safe to call the cleanup functions with an error set by saving and restoring exceptions. --- gi/pygi-marshal-cleanup.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/gi/pygi-marshal-cleanup.c b/gi/pygi-marshal-cleanup.c index b4d04bc5..0e4eda3f 100644 --- a/gi/pygi-marshal-cleanup.c +++ b/gi/pygi-marshal-cleanup.c @@ -93,6 +93,11 @@ pygi_marshal_cleanup_args_from_py_marshal_success (PyGIInvokeState *state, PyGICallableCache *cache) { gssize i; + PyObject *error_type, *error_value, *error_traceback; + gboolean have_error = !!PyErr_Occurred (); + + if (have_error) + PyErr_Fetch (&error_type, &error_value, &error_traceback); for (i = 0; i < _pygi_callable_cache_args_len (cache); i++) { PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (cache, i); @@ -112,6 +117,9 @@ pygi_marshal_cleanup_args_from_py_marshal_success (PyGIInvokeState *state, state->args[i].arg_cleanup_data = NULL; } } + + if (have_error) + PyErr_Restore (error_type, error_value, error_traceback); } void @@ -119,6 +127,12 @@ pygi_marshal_cleanup_args_to_py_marshal_success (PyGIInvokeState *state, PyGICallableCache *cache) { GSList *cache_item; + PyObject *error_type, *error_value, *error_traceback; + gboolean have_error = !!PyErr_Occurred (); + + if (have_error) + PyErr_Fetch (&error_type, &error_value, &error_traceback); + /* clean up the return if available */ if (cache->return_cache != NULL) { PyGIMarshalCleanupFunc cleanup_func = cache->return_cache->to_py_cleanup; @@ -153,6 +167,9 @@ pygi_marshal_cleanup_args_to_py_marshal_success (PyGIInvokeState *state, cache_item = cache_item->next; } + + if (have_error) + PyErr_Restore (error_type, error_value, error_traceback); } void @@ -161,6 +178,11 @@ pygi_marshal_cleanup_args_from_py_parameter_fail (PyGIInvokeState *state, gssize failed_arg_index) { gssize i; + PyObject *error_type, *error_value, *error_traceback; + gboolean have_error = !!PyErr_Occurred (); + + if (have_error) + PyErr_Fetch (&error_type, &error_value, &error_traceback); state->failed = TRUE; @@ -192,6 +214,9 @@ pygi_marshal_cleanup_args_from_py_parameter_fail (PyGIInvokeState *state, } state->args[i].arg_cleanup_data = NULL; } + + if (have_error) + PyErr_Restore (error_type, error_value, error_traceback); } void -- 2.18.0