From 57dbd6a21bd21e6be5ab3c106b1b8a3b3c725927 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Tue, 24 Nov 2020 12:37:08 +0900 Subject: [PATCH] Imported Upstream version 3.31.2 --- .gitlab-ci.yml | 16 +- .gitlab-ci/Dockerfile | 6 +- .gitlab-ci/Dockerfile.gtk4 | 12 +- .gitlab-ci/run-docker-gtk4.sh | 2 +- .gitlab-ci/run-docker.sh | 4 +- .gitlab-ci/test-docker-gtk4.sh | 10 +- .gitlab-ci/test-docker-old.sh | 10 +- .gitlab-ci/test-flatpak.sh | 4 +- NEWS | 32 ++++ PKG-INFO.in | 2 +- gi/gimodule.c | 24 +-- gi/overrides/GLib.py | 23 +-- gi/overrides/GObject.py | 49 ++--- gi/overrides/Gdk.py | 141 +++++++------- gi/overrides/Gtk.py | 54 ++---- gi/pygi-basictype.c | 6 +- gi/pygi-foreign-cairo.c | 10 +- gi/pygi-info.c | 21 ++ meson.build | 4 +- pygobject.doap | 2 +- setup.py | 41 +++- tests/test_cairo.py | 21 ++ tests/test_gdbus.py | 7 + tests/test_generictreemodel.py | 2 +- tests/test_gobject.py | 9 + tests/test_overrides_gdk.py | 96 ++++++++-- tests/test_overrides_glib.py | 138 ++++++++++++++ tests/test_overrides_gobject.py | 268 ++++++++++++++++++++++++++ tests/test_overrides_gtk.py | 326 +++++++++++++++++++++++++++++++- tests/test_repository.py | 51 +++-- tests/test_subprocess.py | 25 +++ 31 files changed, 1161 insertions(+), 255 deletions(-) create mode 100644 tests/test_overrides_gobject.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a1a569b..c7d6633 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: registry.gitlab.gnome.org/gnome/pygobject/main:v8 +image: registry.gitlab.gnome.org/gnome/pygobject/main:v9 stages: - build_and_test @@ -34,7 +34,7 @@ coverage: paths: - coverage/ variables: - PYENV_VERSION: "3.6.6" + PYENV_VERSION: "3.6.7" script: - bash -x ./.gitlab-ci/coverage-docker.sh @@ -91,12 +91,12 @@ python3.5: python3.6: variables: - PYENV_VERSION: "3.6.6" + PYENV_VERSION: "3.6.7" <<: *defaults python3.7: variables: - PYENV_VERSION: "3.7.0-debug" + PYENV_VERSION: "3.7.1-debug" <<: *defaults pypy2: @@ -114,12 +114,18 @@ pypy3: xenial-i386-py2: stage: build_and_test image: registry.gitlab.gnome.org/gnome/pygobject/old:v2 + artifacts: + paths: + - coverage/ script: - bash -x ./.gitlab-ci/test-docker-old.sh gtk4: stage: build_and_test - image: registry.gitlab.gnome.org/gnome/pygobject/gtk4:v1 + image: registry.gitlab.gnome.org/gnome/pygobject/gtk4:v2 + artifacts: + paths: + - coverage/ script: - bash -x ./.gitlab-ci/test-docker-gtk4.sh diff --git a/.gitlab-ci/Dockerfile b/.gitlab-ci/Dockerfile index a72c718..48e2960 100644 --- a/.gitlab-ci/Dockerfile +++ b/.gitlab-ci/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:bionic +FROM ubuntu:cosmic RUN apt-get update && apt-get install -y \ build-essential \ @@ -41,8 +41,8 @@ RUN curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/p RUN pyenv install --debug 2.7.15 RUN pyenv install 3.5.6 -RUN pyenv install 3.6.6 -RUN pyenv install --debug 3.7.0 +RUN pyenv install 3.6.7 +RUN pyenv install --debug 3.7.1 RUN pyenv install pypy2.7-6.0.0 RUN pyenv install pypy3.5-6.0.0 diff --git a/.gitlab-ci/Dockerfile.gtk4 b/.gitlab-ci/Dockerfile.gtk4 index 297a70d..6420e66 100644 --- a/.gitlab-ci/Dockerfile.gtk4 +++ b/.gitlab-ci/Dockerfile.gtk4 @@ -1,4 +1,4 @@ -FROM registry.gitlab.gnome.org/gnome/pygobject/main:v8 +FROM registry.gitlab.gnome.org/gnome/pygobject/main:v9 USER root @@ -15,14 +15,6 @@ RUN apt-get update && apt-get install -y \ wayland-protocols \ && rm -rf /var/lib/apt/lists/* -RUN git clone https://gitlab.freedesktop.org/wayland/wayland.git \ - && cd wayland \ - && git checkout 1.16.0 \ - && ./autogen.sh --disable-documentation \ - && make -j8 && make install \ - && cd .. \ - && rm -Rf wayland - RUN git clone https://gitlab.gnome.org/GNOME/gtk.git \ && cd gtk \ && git checkout 833442e1e29e5 \ @@ -33,4 +25,4 @@ RUN git clone https://gitlab.gnome.org/GNOME/gtk.git \ && rm -Rf gtk USER user -ENV PYENV_VERSION 3.7.0-debug +ENV PYENV_VERSION 3.7.1-debug diff --git a/.gitlab-ci/run-docker-gtk4.sh b/.gitlab-ci/run-docker-gtk4.sh index f6c979d..8bd0a16 100755 --- a/.gitlab-ci/run-docker-gtk4.sh +++ b/.gitlab-ci/run-docker-gtk4.sh @@ -2,7 +2,7 @@ set -e -TAG="registry.gitlab.gnome.org/gnome/pygobject/gtk4:v1" +TAG="registry.gitlab.gnome.org/gnome/pygobject/gtk4:v2" sudo docker build --tag "${TAG}" --file "Dockerfile.gtk4" . sudo docker run --rm --security-opt label=disable \ diff --git a/.gitlab-ci/run-docker.sh b/.gitlab-ci/run-docker.sh index bcfd707..fe0cfc4 100755 --- a/.gitlab-ci/run-docker.sh +++ b/.gitlab-ci/run-docker.sh @@ -2,10 +2,10 @@ set -e -TAG="registry.gitlab.gnome.org/gnome/pygobject/main:v8" +TAG="registry.gitlab.gnome.org/gnome/pygobject/main:v9" sudo docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \ --file "Dockerfile" . -sudo docker run -e PYENV_VERSION='3.7.0-debug' --rm --security-opt label=disable \ +sudo docker run -e PYENV_VERSION='3.7.1-debug' --rm --security-opt label=disable \ --volume "$(pwd)/..:/home/user/app" --workdir "/home/user/app" \ --tty --interactive "${TAG}" bash diff --git a/.gitlab-ci/test-docker-gtk4.sh b/.gitlab-ci/test-docker-gtk4.sh index e36c715..0dd1219 100755 --- a/.gitlab-ci/test-docker-gtk4.sh +++ b/.gitlab-ci/test-docker-gtk4.sh @@ -3,13 +3,17 @@ set -e # ccache setup -mkdir -p _ccache export CCACHE_BASEDIR="$(pwd)" export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache" +COV_DIR="$(pwd)/coverage" +export COVERAGE_FILE="${COV_DIR}/.coverage.${CI_JOB_NAME}" +mkdir -p "${COV_DIR}" +mkdir -p "${CCACHE_DIR}" # test python -m pip install git+https://github.com/pygobject/pycairo.git -python -m pip install pytest pytest-faulthandler +python -m pip install pytest pytest-faulthandler coverage g-ir-inspect Gtk --version=4.0 --print-typelibs export TEST_GTK_VERSION=4.0 -xvfb-run -a python setup.py test +python setup.py build_tests +xvfb-run -a python -m coverage run tests/runtests.py diff --git a/.gitlab-ci/test-docker-old.sh b/.gitlab-ci/test-docker-old.sh index 74c81c7..91312c7 100755 --- a/.gitlab-ci/test-docker-old.sh +++ b/.gitlab-ci/test-docker-old.sh @@ -7,11 +7,15 @@ virtualenv --python=python _venv source _venv/bin/activate # ccache setup -mkdir -p _ccache export CCACHE_BASEDIR="$(pwd)" export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache" +COV_DIR="$(pwd)/coverage" +export COVERAGE_FILE="${COV_DIR}/.coverage.${CI_JOB_NAME}" +mkdir -p "${COV_DIR}" +mkdir -p "${CCACHE_DIR}" # test python -m pip install git+https://github.com/pygobject/pycairo.git -python -m pip install pytest pytest-faulthandler -xvfb-run -a python setup.py test +python -m pip install pytest pytest-faulthandler coverage +python setup.py build_tests +xvfb-run -a python -m coverage run tests/runtests.py diff --git a/.gitlab-ci/test-flatpak.sh b/.gitlab-ci/test-flatpak.sh index 3e3a992..3ca5a74 100755 --- a/.gitlab-ci/test-flatpak.sh +++ b/.gitlab-ci/test-flatpak.sh @@ -2,5 +2,5 @@ set -e -python3 -m pip install --user pytest -python3 setup.py test +python3 -m pip install --user pytest pytest-faulthandler +python3 setup.py test -s diff --git a/NEWS b/NEWS index 1520fba..c569da5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,35 @@ +3.31.2 - 2018-12-15 +------------------- + +* Changes included in 3.30.4 +* GLib.Variant.keys: correctly raise TypeError for non-dict types +* GLib.Variant: implement __bool__ for maybe types +* cairo: Fix GValue converters in case of NULL +* setup.py: Print an install command hint when pkg-config is missing +* pygi-info: wrap g_union_info_get_alignment() + :mr:`105` (:user:`Tomasz Miąsko `) + + +3.30.4 - 2018-11-30 +------------------- + +* gtk overrides: Fix rows getting inserted on the wrong level with + TreeStore.insert_before/insert_after if parent=None. + :issue:`281` (3.30 regression, thanks to :user:`Cian Wilson ` + for the report) + + +3.30.3 - 2018-11-27 +------------------- + +* GValue: fall back to the custom C marshaller to support fundamental types. + This makes GValue work with GstFraction. :issue:`280` +* GValue: Work around wrong annotations for GVariant +* Fix GObject attribute access during instance init which can lead to errors + with __getattr__ implementations of subclasses. This lead to criticals when + instantiating Gio.DBusProxy. :issue:`267` + + 3.31.1 - 2018-11-17 ------------------- diff --git a/PKG-INFO.in b/PKG-INFO.in index c357cc3..34891d7 100644 --- a/PKG-INFO.in +++ b/PKG-INFO.in @@ -12,7 +12,7 @@ Description: Python bindings for GObject Introspection Platform: POSIX, Windows Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: C diff --git a/gi/gimodule.c b/gi/gimodule.c index 3a61616..7ff9b0e 100644 --- a/gi/gimodule.c +++ b/gi/gimodule.c @@ -1066,7 +1066,7 @@ pygobject__g_instance_init(GTypeInstance *instance, } /* XXX: used for Gtk.Template */ - if (PyObject_HasAttrString (wrapper, "__dontuse_ginstance_init__")) { + if (PyObject_HasAttrString ((PyObject*) Py_TYPE (wrapper), "__dontuse_ginstance_init__")) { result = PyObject_CallMethod (wrapper, "__dontuse_ginstance_init__", NULL); if (result == NULL) PyErr_Print (); @@ -1708,11 +1708,11 @@ find_vfunc_info (GIBaseInfo *vfunc_info, GIFieldInfo **field_info_ret) { GType ancestor_g_type = 0; - int length, i; GIBaseInfo *ancestor_info; GIStructInfo *struct_info; gpointer implementor_class = NULL; gboolean is_interface = FALSE; + GIFieldInfo *field_info; ancestor_info = g_base_info_get_container (vfunc_info); is_interface = g_base_info_get_type (ancestor_info) == GI_INFO_TYPE_INTERFACE; @@ -1743,28 +1743,18 @@ find_vfunc_info (GIBaseInfo *vfunc_info, *implementor_class_ret = implementor_class; - length = g_struct_info_get_n_fields (struct_info); - for (i = 0; i < length; i++) { - GIFieldInfo *field_info; + field_info = g_struct_info_find_field (struct_info, + g_base_info_get_name ( (GIBaseInfo*) vfunc_info)); + if (field_info != NULL) { GITypeInfo *type_info; - field_info = g_struct_info_get_field (struct_info, i); - - if (strcmp (g_base_info_get_name ( (GIBaseInfo*) field_info), - g_base_info_get_name ( (GIBaseInfo*) vfunc_info)) != 0) { - g_base_info_unref (field_info); - continue; - } - type_info = g_field_info_get_type (field_info); if (g_type_info_get_tag (type_info) == GI_TYPE_TAG_INTERFACE) { - g_base_info_unref (type_info); *field_info_ret = field_info; - break; + } else { + g_base_info_unref (field_info); } - g_base_info_unref (type_info); - g_base_info_unref (field_info); } g_base_info_unref (struct_info); diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py index d717d5f..e0ac233 100644 --- a/gi/overrides/GLib.py +++ b/gi/overrides/GLib.py @@ -384,15 +384,12 @@ class Variant(GLib.Variant): # Array, dict, tuple if self.get_type_string().startswith('a') or self.get_type_string().startswith('('): return self.n_children() != 0 - if self.get_type_string() in ['v']: - # unpack works recursively, hence bool also works recursively - return bool(self.unpack()) - # Everything else is True - return True + # unpack works recursively, hence bool also works recursively + return bool(self.unpack()) def keys(self): if not self.get_type_string().startswith('a{'): - return TypeError, 'GVariant type %s is not a dictionary' % self.get_type_string() + raise TypeError('GVariant type %s is not a dictionary' % self.get_type_string()) res = [] for i in range(self.n_children()): @@ -825,7 +822,7 @@ def _child_watch_add_get_args(priority_or_pid, pid_or_callback, *args, **kwargs) if 'data' in kwargs: if user_data: raise TypeError('got multiple values for "data" argument') - user_data = [kwargs['data']] + user_data = (kwargs['data'],) return priority, pid, callback, user_data @@ -861,14 +858,10 @@ def filename_from_utf8(utf8string, len=-1): __all__.append('filename_from_utf8') -# backwards compatible API for renamed function -if not hasattr(GLib, 'unix_signal_add_full'): - def add_full_compat(*args): - warnings.warn('GLib.unix_signal_add_full() was renamed to GLib.unix_signal_add()', - PyGIDeprecationWarning) - return GLib.unix_signal_add(*args) - - GLib.unix_signal_add_full = add_full_compat +if hasattr(GLib, "unix_signal_add"): + unix_signal_add_full = GLib.unix_signal_add + __all__.append('unix_signal_add_full') + deprecated_attr("GLib", "unix_signal_add_full", "GLib.unix_signal_add") # obsolete constants for backwards compatibility diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py index 4a6102d..a89ddc7 100644 --- a/gi/overrides/GObject.py +++ b/gi/overrides/GObject.py @@ -159,20 +159,12 @@ for name in ['PARAM_CONSTRUCT', 'PARAM_CONSTRUCT_ONLY', 'PARAM_LAX_VALIDATION', __all__.append(name) # PARAM_READWRITE should come from the gi module but cannot due to: -# https://bugzilla.gnome.org/show_bug.cgi?id=687615 +# https://gitlab.gnome.org/GNOME/gobject-introspection/issues/75 PARAM_READWRITE = GObjectModule.ParamFlags.READABLE | \ GObjectModule.ParamFlags.WRITABLE +deprecated_attr("GObject", "PARAM_READWRITE", "GObject.ParamFlags.READWRITE") __all__.append("PARAM_READWRITE") -# READWRITE is part of ParamFlags since glib 2.42. Only mark PARAM_READWRITE as -# deprecated in case ParamFlags.READWRITE is available. Also include the glib -# version in the warning so it's clear that this needs a newer glib, unlike -# the other ParamFlags related deprecations. -# https://bugzilla.gnome.org/show_bug.cgi?id=726037 -if hasattr(GObjectModule.ParamFlags, "READWRITE"): - deprecated_attr("GObject", "PARAM_READWRITE", - "GObject.ParamFlags.READWRITE (glib 2.42+)") - # Deprecated, use: GObject.SignalFlags.* directly for name in ['SIGNAL_ACTION', 'SIGNAL_DETAILED', 'SIGNAL_NO_HOOKS', @@ -268,14 +260,16 @@ class Value(GObjectModule.Value): if isinstance(py_value, text_type): py_value = py_value.encode('UTF-8') else: - raise ValueError("Expected string or unicode but got %s%s" % - (py_value, type(py_value))) + raise TypeError("Expected string or unicode but got %s%s" % + (py_value, type(py_value))) else: - raise ValueError("Expected string but got %s%s" % - (py_value, type(py_value))) + raise TypeError("Expected string but got %s%s" % + (py_value, type(py_value))) self.set_string(py_value) elif gtype == TYPE_PARAM: self.set_param(py_value) + elif gtype == TYPE_PYOBJECT: + self.set_boxed(py_value) elif gtype.is_a(TYPE_ENUM): self.set_enum(py_value) elif gtype.is_a(TYPE_FLAGS): @@ -286,18 +280,14 @@ class Value(GObjectModule.Value): self.set_pointer(py_value) elif gtype.is_a(TYPE_OBJECT): self.set_object(py_value) - elif gtype == TYPE_UNICHAR: - self.set_uint(int(py_value)) - # elif gtype == TYPE_OVERRIDE: - # pass elif gtype == TYPE_GTYPE: self.set_gtype(py_value) elif gtype == TYPE_VARIANT: self.set_variant(py_value) - elif gtype == TYPE_PYOBJECT: - self.set_boxed(py_value) else: - raise TypeError("Unknown value type %s" % gtype) + # Fall back to _gvalue_set which handles some more cases + # like fundamentals for which a converter is registered + _gi._gvalue_set(self, py_value) def get_value(self): gtype = self.g_type @@ -326,6 +316,8 @@ class Value(GObjectModule.Value): return self.get_double() elif gtype == TYPE_STRING: return self.get_string() + elif gtype == TYPE_PYOBJECT: + return self.get_boxed() elif gtype == TYPE_PARAM: return self.get_param() elif gtype.is_a(TYPE_ENUM): @@ -338,16 +330,16 @@ class Value(GObjectModule.Value): return self.get_pointer() elif gtype.is_a(TYPE_OBJECT): return self.get_object() - elif gtype == TYPE_UNICHAR: - return self.get_uint() elif gtype == TYPE_GTYPE: return self.get_gtype() elif gtype == TYPE_VARIANT: - return self.get_variant() - elif gtype == TYPE_PYOBJECT: - pass - else: + # get_variant was missing annotations + # https://gitlab.gnome.org/GNOME/glib/merge_requests/492 + return self.dup_variant() + elif gtype == _gi.TYPE_INVALID: return None + else: + return _gi._gvalue_get(self) def __repr__(self): return '' % (self.g_type.name, self.get_value()) @@ -423,8 +415,7 @@ def signal_query(id_or_name, type_=None): id_or_name = signal_lookup(id_or_name, type_) res = GObjectModule.signal_query(id_or_name) - if res is None: - return None + assert res is not None if res.signal_id == 0: return None diff --git a/gi/overrides/Gdk.py b/gi/overrides/Gdk.py index 44914db..5aea65f 100644 --- a/gi/overrides/Gdk.py +++ b/gi/overrides/Gdk.py @@ -27,6 +27,8 @@ from ..module import get_introspection_module from gi import PyGIDeprecationWarning, require_version Gdk = get_introspection_module('Gdk') +GDK2 = Gdk._version == '2.0' +GDK3 = Gdk._version == '3.0' __all__ = [] @@ -39,7 +41,7 @@ try: except (ValueError, ImportError): pass -if hasattr(Gdk, 'Color'): +if GDK2 or GDK3: # Gdk.Color was deprecated since 3.14 and dropped in Gtk+-4.0 class Color(Gdk.Color): MAX_VALUE = 65535 @@ -81,7 +83,7 @@ if hasattr(Gdk, 'Color'): Color = override(Color) __all__.append('Color') -if hasattr(Gdk, 'RGBA'): +if GDK3: # Introduced since Gtk+-3.0 class RGBA(Gdk.RGBA): def __init__(self, red=1.0, green=1.0, blue=1.0, alpha=1.0): @@ -121,7 +123,7 @@ if hasattr(Gdk, 'RGBA'): RGBA = override(RGBA) __all__.append('RGBA') -if Gdk._version == '2.0': +if GDK2: class Rectangle(Gdk.Rectangle): def __init__(self, x, y, width, height): @@ -136,7 +138,7 @@ if Gdk._version == '2.0': Rectangle = override(Rectangle) __all__.append('Rectangle') -else: +elif GDK3: # Newer GTK+/gobject-introspection (3.17.x) include GdkRectangle in the # typelib. See https://bugzilla.gnome.org/show_bug.cgi?id=748832 and # https://bugzilla.gnome.org/show_bug.cgi?id=748833 @@ -154,14 +156,14 @@ else: __all__.append('rectangle_intersect') __all__.append('rectangle_union') -if Gdk._version == '2.0': +if GDK2: class Drawable(Gdk.Drawable): def cairo_create(self): return Gdk.cairo_create(self) Drawable = override(Drawable) __all__.append('Drawable') -elif Gdk._version == '3.0': +elif GDK3: class Window(Gdk.Window): def __new__(cls, parent, attributes, attributes_mask): # Gdk.Window had to be made abstract, @@ -177,7 +179,7 @@ elif Gdk._version == '3.0': Window = override(Window) __all__.append('Window') -if Gdk._version in ("2.0", "3.0"): +if GDK2 or GDK3: Gdk.EventType._2BUTTON_PRESS = getattr(Gdk.EventType, "2BUTTON_PRESS") Gdk.EventType._3BUTTON_PRESS = getattr(Gdk.EventType, "3BUTTON_PRESS") @@ -215,7 +217,7 @@ if Gdk._version in ("2.0", "3.0"): Gdk.EventType.UNMAP: 'any', } - if Gdk._version == '2.0': + if GDK2: _UNION_MEMBERS[Gdk.EventType.NO_EXPOSE] = 'no_expose' if hasattr(Gdk.EventType, 'TOUCH_BEGIN'): @@ -315,85 +317,76 @@ if Gdk._version in ("2.0", "3.0"): DragContext = override(DragContext) __all__.append('DragContext') + class Cursor(Gdk.Cursor): + + def __new__(cls, *args, **kwds): + arg_len = len(args) + kwd_len = len(kwds) + total_len = arg_len + kwd_len + + if total_len == 1: + # Since g_object_newv (super.__new__) does not seem valid for + # direct use with GdkCursor, we must assume usage of at least + # one of the C constructors to be valid. + return cls.new(*args, **kwds) + + elif total_len == 2: + warnings.warn('Calling "Gdk.Cursor(display, cursor_type)" has been deprecated. ' + 'Please use Gdk.Cursor.new_for_display(display, cursor_type). ' + 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', + PyGIDeprecationWarning) + return cls.new_for_display(*args, **kwds) + + elif total_len == 4: + warnings.warn('Calling "Gdk.Cursor(display, pixbuf, x, y)" has been deprecated. ' + 'Please use Gdk.Cursor.new_from_pixbuf(display, pixbuf, x, y). ' + 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', + PyGIDeprecationWarning) + return cls.new_from_pixbuf(*args, **kwds) + + elif total_len == 6: + if not GDK2: + # pixmaps don't exist in Gdk 3.0 + raise ValueError("Wrong number of parameters") + + warnings.warn('Calling "Gdk.Cursor(source, mask, fg, bg, x, y)" has been deprecated. ' + 'Please use Gdk.Cursor.new_from_pixmap(source, mask, fg, bg, x, y). ' + 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', + PyGIDeprecationWarning) + return cls.new_from_pixmap(*args, **kwds) -class Cursor(Gdk.Cursor): - - def __new__(cls, *args, **kwds): - arg_len = len(args) - kwd_len = len(kwds) - total_len = arg_len + kwd_len - - if total_len == 1: - if Gdk._version == "4.0": - raise ValueError("Wrong number of parameters") - # Since g_object_newv (super.__new__) does not seem valid for - # direct use with GdkCursor, we must assume usage of at least - # one of the C constructors to be valid. - return cls.new(*args, **kwds) - - elif total_len == 2: - warnings.warn('Calling "Gdk.Cursor(display, cursor_type)" has been deprecated. ' - 'Please use Gdk.Cursor.new_for_display(display, cursor_type). ' - 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', - PyGIDeprecationWarning) - return cls.new_for_display(*args, **kwds) - - elif total_len == 4: - warnings.warn('Calling "Gdk.Cursor(display, pixbuf, x, y)" has been deprecated. ' - 'Please use Gdk.Cursor.new_from_pixbuf(display, pixbuf, x, y). ' - 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', - PyGIDeprecationWarning) - return cls.new_from_pixbuf(*args, **kwds) - - elif total_len == 6: - if Gdk._version != '2.0': - # pixmaps don't exist in Gdk 3.0 + else: raise ValueError("Wrong number of parameters") - warnings.warn('Calling "Gdk.Cursor(source, mask, fg, bg, x, y)" has been deprecated. ' - 'Please use Gdk.Cursor.new_from_pixmap(source, mask, fg, bg, x, y). ' - 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', - PyGIDeprecationWarning) - return cls.new_from_pixmap(*args, **kwds) - - else: - raise ValueError("Wrong number of parameters") - + Cursor = override(Cursor) + __all__.append('Cursor') -Cursor = override(Cursor) -__all__.append('Cursor') - -if hasattr(Gdk, 'color_parse'): # Gdk.Color was deprecated since 3.14 and dropped in Gtk+-4.0 color_parse = strip_boolean_result(Gdk.color_parse) __all__.append('color_parse') + # Note, we cannot override the entire class as Gdk.Atom has no gtype, so just + # hack some individual methods + def _gdk_atom_str(atom): + n = atom.name() + if n: + return n + # fall back to atom index + return 'Gdk.Atom<%i>' % hash(atom) + + def _gdk_atom_repr(atom): + n = atom.name() + if n: + return 'Gdk.Atom.intern("%s", False)' % n + # fall back to atom index + return '' % hash(atom) -# Note, we cannot override the entire class as Gdk.Atom has no gtype, so just -# hack some individual methods -def _gdk_atom_str(atom): - n = atom.name() - if n: - return n - # fall back to atom index - return 'Gdk.Atom<%i>' % hash(atom) - - -def _gdk_atom_repr(atom): - n = atom.name() - if n: - return 'Gdk.Atom.intern("%s", False)' % n - # fall back to atom index - return '' % hash(atom) - - -if Gdk._version in ("2.0", "3.0"): Gdk.Atom.__str__ = _gdk_atom_str Gdk.Atom.__repr__ = _gdk_atom_repr # constants -if Gdk._version == '3.0': +if GDK3: SELECTION_PRIMARY = Gdk.atom_intern('PRIMARY', True) __all__.append('SELECTION_PRIMARY') @@ -442,6 +435,6 @@ if Gdk._version == '3.0': SELECTION_TYPE_STRING = Gdk.atom_intern('STRING', True) __all__.append('SELECTION_TYPE_STRING') -if Gdk._version in ('2.0', '3.0'): +if GDK2 or GDK3: import sys initialized, argv = Gdk.init_check(sys.argv) diff --git a/gi/overrides/Gtk.py b/gi/overrides/Gtk.py index 7e00c61..ba0a71d 100644 --- a/gi/overrides/Gtk.py +++ b/gi/overrides/Gtk.py @@ -381,12 +381,7 @@ if Gtk._version in ("2.0", "3.0"): def _process_action(group_source, name, stock_id=None, label=None, accelerator=None, tooltip=None, entry_value=0): action = RadioAction(name=name, label=label, tooltip=tooltip, stock_id=stock_id, value=entry_value) - # FIXME: join_group is a patch to Gtk+ 3.0 - # otherwise we can't effectively add radio actions to a - # group. Should we depend on 3.0 and error out here - # or should we offer the functionality via a compat - # C module? - if hasattr(action, 'join_group'): + if Gtk._version == '3.0': action.join_group(group_source) if value == entry_value: @@ -466,8 +461,7 @@ __all__.append('MenuItem') def _get_utf8_length(string): - if not isinstance(string, string_types): - raise TypeError('must be a string') + assert isinstance(string, string_types) if not isinstance(string, bytes): string = string.encode("utf-8") return len(string) @@ -553,8 +547,7 @@ class Dialog(Gtk.Dialog, Container): 'Please use the "add_buttons" method for adding buttons. ' 'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations', PyGTKDeprecationWarning, stacklevel=stacklevel) - if 'buttons' in new_kwargs: - del new_kwargs['buttons'] + new_kwargs.pop('buttons', None) else: add_buttons = None @@ -600,15 +593,15 @@ class Dialog(Gtk.Dialog, Container): """ def _button(b): while b: - t, r = b[0:2] + try: + t, r = b[0:2] + except ValueError: + raise ValueError('Must pass an even number of arguments') b = b[2:] yield t, r - try: - for text, response in _button(args): - self.add_button(text, response) - except (IndexError): - raise TypeError('Must pass an even number of arguments') + for text, response in _button(args): + self.add_button(text, response) Dialog = override(Dialog) @@ -720,13 +713,6 @@ __all__.append('RecentInfo') class TextBuffer(Gtk.TextBuffer): - def _get_or_create_tag_table(self): - table = self.get_tag_table() - if table is None: - table = Gtk.TextTagTable() - self.set_tag_table(table) - - return table def create_tag(self, tag_name=None, **properties): """Creates a tag and adds it to the tag table of the TextBuffer. @@ -753,7 +739,7 @@ class TextBuffer(Gtk.TextBuffer): """ tag = Gtk.TextTag(name=tag_name, **properties) - self._get_or_create_tag_table().add(tag) + self.get_tag_table().add(tag) return tag def create_mark(self, mark_name, where, left_gravity=False): @@ -830,11 +816,7 @@ class TreeModel(Gtk.TreeModel): index = len(self) + key if index < 0: raise IndexError("row index is out of bounds: %d" % key) - try: - aiter = self.get_iter(index) - except ValueError: - raise IndexError("could not find tree path '%s'" % key) - return aiter + return self.get_iter(index) else: try: aiter = self.get_iter(key) @@ -912,11 +894,7 @@ class TreeModel(Gtk.TreeModel): def set_row(self, treeiter, row): converted_row, columns = self._convert_row(row) for column in columns: - value = row[column] - if value is None: - continue # None means skip this row - - self.set_value(treeiter, column, value) + self.set_value(treeiter, column, row[column]) def _convert_value(self, column, value): '''Convert value to a GObject.Value of the expected type''' @@ -1086,8 +1064,8 @@ class TreeModelRow(object): elif isinstance(iter_or_path, Gtk.TreeIter): self.iter = iter_or_path else: - raise TypeError("expected Gtk.TreeIter or Gtk.TreePath, \ - %s found" % type(iter_or_path).__name__) + raise TypeError("expected Gtk.TreeIter or Gtk.TreePath, " + "%s found" % type(iter_or_path).__name__) @property def path(self): @@ -1285,6 +1263,8 @@ class TreeStore(Gtk.TreeStore, TreeModel, TreeSortable): if sibling is None: position = -1 else: + if parent is None: + parent = self.iter_parent(sibling) position = self.get_path(sibling).get_indices()[-1] return self._do_insert(parent, position, row) @@ -1295,6 +1275,8 @@ class TreeStore(Gtk.TreeStore, TreeModel, TreeSortable): if sibling is None: position = 0 else: + if parent is None: + parent = self.iter_parent(sibling) position = self.get_path(sibling).get_indices()[-1] + 1 return self._do_insert(parent, position, row) diff --git a/gi/pygi-basictype.c b/gi/pygi-basictype.c index 82b43c5..dafddf2 100644 --- a/gi/pygi-basictype.c +++ b/gi/pygi-basictype.c @@ -259,7 +259,11 @@ pygi_gtype_from_py (PyObject *py_arg, GType *type) GType temp = pyg_type_from_object (py_arg); if (temp == 0) { - PyErr_Format (PyExc_TypeError, "Must be gobject.GType, not %s", + if (!PyErr_Occurred ()) { + PyErr_SetString (PyExc_ValueError, "Invalid GType"); + return FALSE; + } + PyErr_Format (PyExc_TypeError, "Must be GObject.GType, not %s", Py_TYPE (py_arg)->tp_name); return FALSE; } diff --git a/gi/pygi-foreign-cairo.c b/gi/pygi-foreign-cairo.c index 718f9a0..c99847b 100644 --- a/gi/pygi-foreign-cairo.c +++ b/gi/pygi-foreign-cairo.c @@ -118,7 +118,7 @@ cairo_context_from_gvalue (const GValue *value) /* PycairoContext_FromContext steals a ref, so we dup it out of the GValue. */ cairo_t *cr = g_value_dup_boxed (value); if (!cr) { - return NULL; + Py_RETURN_NONE; } return PycairoContext_FromContext (cr, &PycairoContext_Type, NULL); @@ -203,7 +203,7 @@ cairo_surface_from_gvalue (const GValue *value) /* PycairoSurface_FromSurface steals a ref, so we dup it out of the GValue. */ cairo_surface_t *surface = g_value_dup_boxed (value); if (!surface) { - return NULL; + Py_RETURN_NONE; } return PycairoSurface_FromSurface (surface, NULL); @@ -308,7 +308,7 @@ cairo_font_face_from_gvalue (const GValue *value) { cairo_font_face_t *font_face = g_value_dup_boxed (value); if (!font_face) { - return NULL; + Py_RETURN_NONE; } return PycairoFontFace_FromFontFace (font_face); @@ -398,7 +398,7 @@ cairo_scaled_font_from_gvalue (const GValue *value) /* PycairoScaledFont_FromScaledFont steals a ref, so we dup it out of the GValue. */ cairo_scaled_font_t *scaled_font = g_value_dup_boxed (value); if (!scaled_font) { - return NULL; + Py_RETURN_NONE; } return PycairoScaledFont_FromScaledFont (scaled_font); @@ -436,7 +436,7 @@ cairo_pattern_from_gvalue (const GValue *value) /* PycairoPattern_FromPattern steals a ref, so we dup it out of the GValue. */ cairo_pattern_t *pattern = g_value_dup_boxed (value); if (!pattern) { - return NULL; + Py_RETURN_NONE; } return PycairoPattern_FromPattern (pattern, NULL); diff --git a/gi/pygi-info.c b/gi/pygi-info.c index 4fca825..5508001 100644 --- a/gi/pygi-info.c +++ b/gi/pygi-info.c @@ -1307,9 +1307,23 @@ _wrap_g_struct_info_is_foreign (PyGIBaseInfo *self) return pygi_gboolean_to_py (g_struct_info_is_foreign (self->info)); } +static PyObject * +_wrap_g_struct_info_find_method (PyGIBaseInfo *self, PyObject *py_name) +{ + return _get_child_info_by_name (self, py_name, g_struct_info_find_method); +} + +static PyObject * +_wrap_g_struct_info_find_field (PyGIBaseInfo *self, PyObject *py_name) +{ + return _get_child_info_by_name (self, py_name, g_struct_info_find_field); +} + static PyMethodDef _PyGIStructInfo_methods[] = { { "get_fields", (PyCFunction) _wrap_g_struct_info_get_fields, METH_NOARGS }, + { "find_field", (PyCFunction) _wrap_g_struct_info_find_field, METH_O }, { "get_methods", (PyCFunction) _wrap_g_struct_info_get_methods, METH_NOARGS }, + { "find_method", (PyCFunction) _wrap_g_struct_info_find_method, METH_O }, { "get_size", (PyCFunction) _wrap_g_struct_info_get_size, METH_NOARGS }, { "get_alignment", (PyCFunction) _wrap_g_struct_info_get_alignment, METH_NOARGS }, { "is_gtype_struct", (PyCFunction) _wrap_g_struct_info_is_gtype_struct, METH_NOARGS }, @@ -2229,10 +2243,17 @@ _wrap_g_union_info_get_size (PyGIBaseInfo *self) return pygi_gsize_to_py (g_union_info_get_size (self->info)); } +static PyObject * +_wrap_g_union_info_get_alignment (PyGIBaseInfo *self) +{ + return pygi_gsize_to_py (g_union_info_get_alignment (self->info)); +} + static PyMethodDef _PyGIUnionInfo_methods[] = { { "get_fields", (PyCFunction) _wrap_g_union_info_get_fields, METH_NOARGS }, { "get_methods", (PyCFunction) _wrap_g_union_info_get_methods, METH_NOARGS }, { "get_size", (PyCFunction) _wrap_g_union_info_get_size, METH_NOARGS }, + { "get_alignment", (PyCFunction) _wrap_g_union_info_get_alignment, METH_NOARGS }, { NULL, NULL, 0 } }; diff --git a/meson.build b/meson.build index db9bca3..5271ce9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pygobject', 'c', - version : '3.31.1', + version : '3.31.2', meson_version : '>= 0.46.0', default_options : [ 'warning_level=1', 'buildtype=debugoptimized']) @@ -17,7 +17,7 @@ python = pymod.find_installation(get_option('python')) python_dep = python.dependency() -glib_version_req = '>= 2.38.0' +glib_version_req = '>= 2.48.0' gi_version_req = '>= 1.46.0' pycairo_version_req = '>= 1.11.1' libffi_version_req = '>= 3.0' diff --git a/pygobject.doap b/pygobject.doap index 63f184f..cca2a48 100644 --- a/pygobject.doap +++ b/pygobject.doap @@ -19,7 +19,7 @@ PyGObject now dynamically accesses any GObject libraries that uses GObject Intro - + C Python diff --git a/setup.py b/setup.py index b4aa14c..f9d5ce6 100755 --- a/setup.py +++ b/setup.py @@ -41,8 +41,8 @@ from distutils import dir_util, log from distutils.spawn import find_executable -PYGOBJECT_VERISON = "3.31.1" -GLIB_VERSION_REQUIRED = "2.38.0" +PYGOBJECT_VERSION = "3.31.2" +GLIB_VERSION_REQUIRED = "2.48.0" GI_VERSION_REQUIRED = "1.46.0" PYCAIRO_VERSION_REQUIRED = "1.11.1" LIBFFI_VERSION_REQUIRED = "3.0" @@ -54,7 +54,7 @@ cairo/pycairo support. Note that this option might get removed in the future. def is_dev_version(): - version = tuple(map(int, PYGOBJECT_VERISON.split("."))) + version = tuple(map(int, PYGOBJECT_VERSION.split("."))) return version[1] % 2 != 0 @@ -84,7 +84,7 @@ def get_version_requirement(pkg_config_name): def get_versions(): - version = PYGOBJECT_VERISON.split(".") + version = PYGOBJECT_VERSION.split(".") assert len(version) == 3 versions = { @@ -114,7 +114,19 @@ def parse_pkg_info(conf_dir): return message -def pkg_config_get_install_hint(pkg_name): +def pkg_config_get_install_hint(): + """Returns an installation hint for installing pkg-config or None""" + + if not sys.platform.startswith("linux"): + return + + if find_executable("apt"): + return "sudo apt install pkg-config" + elif find_executable("dnf"): + return "sudo dnf install pkg-config" + + +def pkg_config_get_package_install_hint(pkg_name): """Returns an installation hint for a pkg-config name or None""" if not sys.platform.startswith("linux"): @@ -148,6 +160,10 @@ class PkgConfigError(Exception): pass +class PkgConfigMissingError(PkgConfigError): + pass + + class PkgConfigMissingPackageError(PkgConfigError): pass @@ -162,7 +178,7 @@ def _run_pkg_config(pkg_name, args, _cache={}): result = subprocess.check_output(command) except OSError as e: if e.errno == errno.ENOENT: - raise PkgConfigError( + raise PkgConfigMissingError( "%r not found.\nArguments: %r" % (command[0], command)) raise PkgConfigError(e) except subprocess.CalledProcessError as e: @@ -181,8 +197,15 @@ def _run_pkg_config(pkg_name, args, _cache={}): def _run_pkg_config_or_exit(pkg_name, args): try: return _run_pkg_config(pkg_name, args) + except PkgConfigMissingError as e: + hint = pkg_config_get_install_hint() + if hint: + raise SystemExit( + "%s\n\nTry installing it with: %r" % (e, hint)) + else: + raise SystemExit(e) except PkgConfigMissingPackageError as e: - hint = pkg_config_get_install_hint(pkg_name) + hint = pkg_config_get_package_install_hint(pkg_name) if hint: raise SystemExit( "%s\n\nTry installing it with: %r" % (e, hint)) @@ -280,7 +303,7 @@ class sdist_gnome(Command): def run(self): # Don't use PEP 440 pre-release versions for GNOME releases - self.distribution.metadata.version = PYGOBJECT_VERISON + self.distribution.metadata.version = PYGOBJECT_VERSION dist_dir = tempfile.mkdtemp() try: @@ -1146,7 +1169,7 @@ class install_pkgconfig(Command): "includedir": "${prefix}/include", "datarootdir": "${prefix}/share", "datadir": "${datarootdir}", - "VERSION": PYGOBJECT_VERISON, + "VERSION": PYGOBJECT_VERSION, } for key, value in config.items(): content = content.replace("@%s@" % key, value) diff --git a/tests/test_cairo.py b/tests/test_cairo.py index 8ccbe15..f4d0d7c 100644 --- a/tests/test_cairo.py +++ b/tests/test_cairo.py @@ -30,6 +30,27 @@ from gi.repository import GObject, Regress @unittest.skipUnless(has_cairo, 'built without cairo support') class Test(unittest.TestCase): + + def test_gvalue_converters(self): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10) + context = cairo.Context(surface) + objects = { + 'CairoContext': context, + 'CairoSurface': surface, + 'CairoFontFace': context.get_font_face(), + 'CairoScaledFont': context.get_scaled_font(), + 'CairoPattern': context.get_source(), + } + for type_name, cairo_obj in objects.items(): + gtype = GObject.type_from_name(type_name) + v = GObject.Value() + assert v.init(gtype) is None + assert v.get_value() is None + v.set_value(None) + assert v.get_value() is None + v.set_value(cairo_obj) + assert v.get_value() == cairo_obj + def test_cairo_context(self): context = Regress.test_cairo_context_full_return() self.assertTrue(isinstance(context, cairo.Context)) diff --git a/tests/test_gdbus.py b/tests/test_gdbus.py index 18315af..6e38abe 100644 --- a/tests/test_gdbus.py +++ b/tests/test_gdbus.py @@ -248,3 +248,10 @@ class TestGDBusClient(unittest.TestCase): self.assertTrue(isinstance(data['error'], Exception)) self.assertTrue('InvalidArgs' in str(data['error']), str(data['error'])) + + def test_instantiate_custom_proxy(self): + class SomeProxy(Gio.DBusProxy): + def __init__(self): + Gio.DBusProxy.__init__(self) + + SomeProxy() diff --git a/tests/test_generictreemodel.py b/tests/test_generictreemodel.py index 5e9d716..acb6028 100644 --- a/tests/test_generictreemodel.py +++ b/tests/test_generictreemodel.py @@ -378,7 +378,7 @@ class TestReturnsAfterError(unittest.TestCase): self.assertEqual(count, 0) def test_get_column_type(self): - with ExceptHook(NotImplementedError, TypeError): + with ExceptHook(NotImplementedError, ValueError): col_type = self.model.get_column_type(0) self.assertEqual(col_type, GObject.TYPE_INVALID) diff --git a/tests/test_gobject.py b/tests/test_gobject.py index bef3e9b..9b4656b 100644 --- a/tests/test_gobject.py +++ b/tests/test_gobject.py @@ -15,6 +15,7 @@ from gi.module import get_introspection_module from gi import _gi import testhelper +from .helper import capture_glib_deprecation_warnings class TestGObjectAPI(unittest.TestCase): @@ -463,6 +464,14 @@ class TestPropertyBindings(unittest.TestCase): self.assertEqual(self.source.int_prop, 1) self.assertEqual(self.target.int_prop, 2) + def test_call_binding(self): + binding = self.source.bind_property('int_prop', self.target, 'int_prop', + GObject.BindingFlags.DEFAULT) + with capture_glib_deprecation_warnings() as warn: + result = binding() + assert len(warn) + assert result is binding + def test_bidirectional_binding(self): binding = self.source.bind_property('int_prop', self.target, 'int_prop', GObject.BindingFlags.BIDIRECTIONAL) diff --git a/tests/test_overrides_gdk.py b/tests/test_overrides_gdk.py index 9c36674..88fc465 100644 --- a/tests/test_overrides_gdk.py +++ b/tests/test_overrides_gdk.py @@ -3,19 +3,28 @@ from __future__ import absolute_import +import re import os import sys import unittest +import pytest +import gi import gi.overrides from gi import PyGIDeprecationWarning try: from gi.repository import Gdk, GdkPixbuf, Gtk - Gdk_version = Gdk._version + GDK4 = Gdk._version == "4.0" except ImportError: Gdk = None - Gdk_version = None + GDK4 = False + +try: + gi.require_foreign('cairo') + has_cairo = True +except ImportError: + has_cairo = False from .helper import capture_glib_deprecation_warnings @@ -24,7 +33,7 @@ from .helper import capture_glib_deprecation_warnings class TestGdk(unittest.TestCase): @unittest.skipIf(sys.platform == "darwin" or os.name == "nt", "crashes") - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_constructor(self): attribute = Gdk.WindowAttr() attribute.window_type = Gdk.WindowType.CHILD @@ -33,7 +42,7 @@ class TestGdk(unittest.TestCase): window = Gdk.Window(None, attribute, attributes_mask) self.assertEqual(window.get_window_type(), Gdk.WindowType.CHILD) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_color(self): color = Gdk.Color(100, 200, 300) self.assertEqual(color.red, 100) @@ -43,7 +52,7 @@ class TestGdk(unittest.TestCase): self.assertEqual(color, Gdk.Color(100, 200, 300)) self.assertNotEqual(color, Gdk.Color(1, 2, 3)) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_color_floats(self): self.assertEqual(Gdk.Color(13107, 21845, 65535), Gdk.Color.from_floats(0.2, 1.0 / 3.0, 1.0)) @@ -57,6 +66,20 @@ class TestGdk(unittest.TestCase): self.assertEqual(Gdk.RGBA.from_color(Gdk.Color(13107, 21845, 65535)), Gdk.RGBA(0.2, 1.0 / 3.0, 1.0, 1.0)) + @unittest.skipIf(GDK4, "not in gdk4") + def test_color_to_floats_attrs(self): + color = Gdk.Color(13107, 21845, 65535) + assert color.red_float == 0.2 + color.red_float = 0 + assert color.red_float == 0 + assert color.green_float == 1.0 / 3.0 + color.green_float = 0 + assert color.green_float == 0 + assert color.blue_float == 1.0 + color.blue_float = 0 + assert color.blue_float == 0 + + @unittest.skipIf(GDK4, "not in gdk4") def test_rgba(self): self.assertEqual(Gdk.RGBA, gi.overrides.Gdk.RGBA) rgba = Gdk.RGBA(0.1, 0.2, 0.3, 0.4) @@ -73,7 +96,26 @@ class TestGdk(unittest.TestCase): self.assertEqual(tuple(Gdk.RGBA(0.1, 0.2, 0.3, 0.4)), (0.1, 0.2, 0.3, 0.4)) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipUnless(GDK4, "only in gdk4") + def test_rgba_gtk4(self): + c = Gdk.RGBA() + assert c.to_string() == "rgba(0,0,0,0)" + + @unittest.skipIf(not has_cairo or GDK4, "not in gdk4") + def test_window(self): + w = Gtk.Window() + w.realize() + window = w.get_window() + with capture_glib_deprecation_warnings(): + assert window.cairo_create() is not None + + @unittest.skipIf(GDK4, "not in gdk4") + def test_drag_context(self): + context = Gdk.DragContext() + # using it this way crashes.. + assert hasattr(context, "finish") + + @unittest.skipIf(GDK4, "not in gdk4") def test_event(self): event = Gdk.Event.new(Gdk.EventType.CONFIGURE) self.assertEqual(event.type, Gdk.EventType.CONFIGURE) @@ -83,7 +125,7 @@ class TestGdk(unittest.TestCase): event.type = Gdk.EventType.SCROLL self.assertRaises(AttributeError, lambda: getattr(event, 'foo_bar')) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_event_touch(self): event = Gdk.Event.new(Gdk.EventType.TOUCH_BEGIN) self.assertEqual(event.type, Gdk.EventType.TOUCH_BEGIN) @@ -96,7 +138,7 @@ class TestGdk(unittest.TestCase): self.assertTrue(event.emulating_pointer) self.assertTrue(event.touch.emulating_pointer) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_event_setattr(self): event = Gdk.Event.new(Gdk.EventType.DRAG_MOTION) event.x_root, event.y_root = 0, 5 @@ -109,12 +151,19 @@ class TestGdk(unittest.TestCase): self.assertFalse(hasattr(event, "foo_bar")) event.foo_bar = 42 - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + # unhandled type + event.type = Gdk.EventType.EVENT_LAST + with pytest.raises(AttributeError): + event.foo_bar + event.foo_bar = 42 + assert event.foo_bar == 42 + + @unittest.skipIf(GDK4, "not in gdk4") def test_event_repr(self): event = Gdk.Event.new(Gdk.EventType.CONFIGURE) self.assertTrue("CONFIGURE" in repr(event)) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_event_structures(self): def button_press_cb(button, event): self.assertTrue(isinstance(event, Gdk.EventButton)) @@ -138,7 +187,7 @@ class TestGdk(unittest.TestCase): Gdk.ModifierType.CONTROL_MASK, Gdk.EventType.BUTTON_PRESS) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_cursor(self): self.assertEqual(Gdk.Cursor, gi.overrides.Gdk.Cursor) with capture_glib_deprecation_warnings(): @@ -170,6 +219,20 @@ class TestGdk(unittest.TestCase): self.assertRaises(ValueError, Gdk.Cursor, 1, 2, 3) + with capture_glib_deprecation_warnings() as warn: + c = Gdk.Cursor(display, Gdk.CursorType.WATCH) + assert len(warn) == 1 + # on macOS the type is switched to PIXMAP behind the scenes + assert c.props.cursor_type in ( + Gdk.CursorType.WATCH, Gdk.CursorType.CURSOR_IS_PIXMAP) + assert c.props.display == display + + @unittest.skipUnless(GDK4, "only gdk4") + def test_cursor_gdk4(self): + Gdk.Cursor() + Gdk.Cursor(name="foo") + Gdk.Cursor(fallback=Gdk.Cursor()) + def test_flags(self): self.assertEqual(Gdk.ModifierType.META_MASK | 0, 0x10000000) self.assertEqual(hex(Gdk.ModifierType.META_MASK), '0x10000000') @@ -185,7 +248,7 @@ class TestGdk(unittest.TestCase): self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK), '') - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_color_parse(self): with capture_glib_deprecation_warnings(): c = Gdk.color_parse('#00FF80') @@ -194,7 +257,7 @@ class TestGdk(unittest.TestCase): self.assertEqual(c.blue, 32896) self.assertEqual(Gdk.color_parse('bogus'), None) - @unittest.skipIf(Gdk_version == "4.0", "not in gdk4") + @unittest.skipIf(GDK4, "not in gdk4") def test_color_representations(self): # __repr__ should generate a string which is parsable when possible # http://docs.python.org/2/reference/datamodel.html#object.__repr__ @@ -204,6 +267,7 @@ class TestGdk(unittest.TestCase): rgba = Gdk.RGBA(red=1.0, green=0.8, blue=0.6, alpha=0.4) self.assertEqual(eval(repr(rgba)), rgba) + @unittest.skipIf(GDK4, "not in gdk4") def test_rectangle_functions(self): # https://bugzilla.gnome.org/show_bug.cgi?id=756364 a = Gdk.Rectangle() @@ -212,3 +276,9 @@ class TestGdk(unittest.TestCase): intersect, rect = Gdk.rectangle_intersect(a, b) self.assertTrue(isinstance(rect, Gdk.Rectangle)) self.assertTrue(isinstance(intersect, bool)) + + @unittest.skipIf(GDK4, "not in gdk4") + def test_atom_repr_str(self): + atom = Gdk.atom_intern("", True) + assert re.match(r"", repr(atom)) + assert re.match(r"Gdk.Atom<\d+>", str(atom)) diff --git a/tests/test_overrides_glib.py b/tests/test_overrides_glib.py index 3467253..ac19ccd 100644 --- a/tests/test_overrides_glib.py +++ b/tests/test_overrides_glib.py @@ -3,13 +3,123 @@ from __future__ import absolute_import +import os import gc import unittest +import tempfile +import socket + +import pytest import gi from gi.repository import GLib from gi._compat import long_, integer_types +from .helper import capture_gi_deprecation_warnings + + +def test_io_add_watch_get_args(): + get_args = GLib._io_add_watch_get_args + func = lambda: None + + # create a closed channel for testing + fd, fn = tempfile.mkstemp() + os.close(fd) + try: + chan = GLib.IOChannel(filename=fn) + chan.shutdown(True) + finally: + os.remove(fn) + + # old way + with capture_gi_deprecation_warnings(): + assert get_args(chan, GLib.IOCondition.IN, func) == ( + chan, 0, GLib.IOCondition.IN, func, tuple()) + + with pytest.raises(TypeError): + get_args(chan, GLib.IOCondition.IN, object()) + + # new way + prio = GLib.PRIORITY_DEFAULT + with capture_gi_deprecation_warnings(): + assert get_args(chan, prio, GLib.IOCondition.IN, func, 99) == \ + (chan, prio, GLib.IOCondition.IN, func, (99,)) + + with pytest.raises(TypeError): + assert get_args(chan, prio, GLib.IOCondition.IN) + + with pytest.raises(TypeError): + assert get_args(chan, prio, 99) + + +@pytest.mark.skipif(os.name != "nt", reason="windows only") +def test_io_add_watch_get_args_win32_socket(): + get_args = GLib._io_add_watch_get_args + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + func = lambda: None + prio = GLib.PRIORITY_DEFAULT + chan = get_args(s, prio, GLib.IOCondition.IN, func)[0] + assert isinstance(chan, GLib.IOChannel) + chan.shutdown(False) + + +def test_threads_init(): + with capture_gi_deprecation_warnings() as w: + GLib.threads_init() + assert len(w) + + +def test_gerror_matches(): + e = GLib.Error(domain=42, code=24) + assert e.matches(42, 24) + + +def test_timeout_add_seconds(): + h = GLib.timeout_add_seconds( + 100, lambda *x: None, 1, 2, 3, priority=GLib.PRIORITY_HIGH_IDLE) + GLib.source_remove(h) + + +def test_iochannel(): + with pytest.raises(TypeError): + GLib.IOChannel() + + +def test_iochannel_write(): + fd, fn = tempfile.mkstemp() + os.close(fd) + chan = GLib.IOChannel(filename=fn, mode="r+") + try: + assert chan.write(b"foo", 2) == 2 + chan.seek(0) + assert chan.read() == b"fo" + finally: + chan.shutdown(True) + + +@pytest.mark.skipif(os.name == "nt", reason="unix only") +def test_has_unix_signal_add(): + with capture_gi_deprecation_warnings(): + GLib.unix_signal_add == GLib.unix_signal_add_full + + +@pytest.mark.skipif(os.name != "nt", reason="windows only") +def test_iochannel_win32(): + fd, fn = tempfile.mkstemp() + closed = False + try: + channel = GLib.IOChannel(hwnd=fd) + try: + assert channel.read() == b"" + finally: + closed = True + channel.shutdown(True) + finally: + if not closed: + os.close(fd) + os.remove(fn) + class TestGVariant(unittest.TestCase): def test_create_simple(self): @@ -25,6 +135,17 @@ class TestGVariant(unittest.TestCase): self.assertTrue(isinstance(variant, GLib.Variant)) self.assertEqual(variant.get_string(), 'hello') + def test_simple_invalid_ops(self): + variant = GLib.Variant('i', 42) + with pytest.raises(TypeError): + len(variant) + + with pytest.raises(TypeError): + variant[0] + + with pytest.raises(TypeError): + variant.keys() + def test_create_variant(self): variant = GLib.Variant('v', GLib.Variant('i', 42)) self.assertTrue(isinstance(variant, GLib.Variant)) @@ -97,6 +218,15 @@ class TestGVariant(unittest.TestCase): self.assertTrue(isinstance(variant, GLib.Variant)) self.assertEqual(variant.unpack(), d) + # init with an iterable + variant = GLib.Variant('a{si}', [("foo", 2)]) + assert variant.unpack() == {'foo': 2} + + with pytest.raises(TypeError): + GLib.Variant('a{si}', [("foo",)]) + with pytest.raises(TypeError): + GLib.Variant('a{si}', [("foo", 1, 2)]) + def test_create_array(self): variant = GLib.Variant('ai', []) self.assertEqual(variant.get_type_string(), 'ai') @@ -471,6 +601,9 @@ class TestGVariant(unittest.TestCase): assert_equal('v', GLib.Variant('i', 42)) assert_not_equal('v', GLib.Variant('i', 42), 'v', GLib.Variant('i', 43)) + assert GLib.Variant('i', 42) != object() + assert not GLib.Variant('i', 42) == object() + def test_bool(self): # Check if the GVariant bool matches the unpacked Pythonic bool @@ -527,6 +660,11 @@ class TestGVariant(unittest.TestCase): assert_equals_bool('v', GLib.Variant('i', 0)) assert_equals_bool('v', GLib.Variant('i', 1)) + # maybe types + assert_equals_bool('mi', 42) + assert_equals_bool('mi', 0) + assert_equals_bool('mi', None) + def test_repr(self): # with C constructor v = GLib.Variant.new_uint32(42) diff --git a/tests/test_overrides_gobject.py b/tests/test_overrides_gobject.py new file mode 100644 index 0000000..37292db --- /dev/null +++ b/tests/test_overrides_gobject.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +import pytest + +from gi import PyGIDeprecationWarning +from gi.repository import GObject, GLib + +from gi._compat import PY2 +from .helper import ignore_gi_deprecation_warnings + + +def test_stop_emission_deprec(): + class TestObject(GObject.GObject): + int_prop = GObject.Property(default=0, type=int) + + obj = TestObject() + + def notify_callback(obj, *args): + with pytest.warns(PyGIDeprecationWarning): + obj.stop_emission("notify::int-prop") + + with pytest.warns(PyGIDeprecationWarning): + obj.emit_stop_by_name("notify::int-prop") + + obj.stop_emission_by_name("notify::int-prop") + + obj.connect("notify::int-prop", notify_callback) + obj.notify("int-prop") + + +def test_signal_parse_name(): + obj = GObject.GObject() + assert GObject.signal_parse_name("notify", obj, True) == (1, 0) + + with pytest.raises(ValueError): + GObject.signal_parse_name("foobar", obj, True) + + +def test_signal_query(): + obj = GObject.GObject() + res = GObject.signal_query("notify", obj) + assert res.signal_name == "notify" + assert res.itype == obj.__gtype__ + + res = GObject.signal_query("foobar", obj) + assert res is None + + +def test_value_repr(): + v = GObject.Value() + assert repr(v) == "" + + v = GObject.Value(int, 0) + assert repr(v) == "" + + +def test_value_no_init(): + v = GObject.Value() + with pytest.raises(TypeError): + v.set_value(0) + v.init(GObject.TYPE_LONG) + assert v.get_value() == 0 + v.set_value(0) + + +def test_value_invalid_type(): + v = GObject.Value() + assert v.g_type == GObject.TYPE_INVALID + assert isinstance(GObject.TYPE_INVALID, GObject.GType) + with pytest.raises(ValueError, match="Invalid GType"): + v.init(GObject.TYPE_INVALID) + + +def test_value_long(): + v = GObject.Value(GObject.TYPE_LONG) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXLONG) + assert v.get_value() == GLib.MAXLONG + + v.set_value(GLib.MINLONG) + assert v.get_value() == GLib.MINLONG + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXLONG + 1) + + with pytest.raises(OverflowError): + v.set_value(GLib.MINLONG - 1) + + +def test_value_ulong(): + v = GObject.Value(GObject.TYPE_ULONG) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXULONG) + assert v.get_value() == GLib.MAXULONG + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXULONG + 1) + + with pytest.raises(OverflowError): + v.set_value(-1) + + +def test_value_uint64(): + v = GObject.Value(GObject.TYPE_UINT64) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXUINT64) + assert v.get_value() == GLib.MAXUINT64 + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXUINT64 + 1) + + with pytest.raises(OverflowError): + v.set_value(-1) + + +def test_value_int64(): + v = GObject.Value(GObject.TYPE_INT64) + assert v.get_value() == 0 + v.set_value(0) + assert v.get_value() == 0 + + v.set_value(GLib.MAXINT64) + assert v.get_value() == GLib.MAXINT64 + v.set_value(GLib.MININT64) + assert v.get_value() == GLib.MININT64 + + with pytest.raises(OverflowError): + v.set_value(GLib.MAXINT64 + 1) + + with pytest.raises(OverflowError): + v.set_value(GLib.MININT64 - 1) + + +def test_value_pointer(): + v = GObject.Value(GObject.TYPE_POINTER) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + v.set_value(0) + assert v.get_value() == 0 + + +def test_value_unichar(): + assert GObject.TYPE_UNICHAR == GObject.TYPE_UINT + + v = GObject.Value(GObject.TYPE_UNICHAR) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + + v.set_value(GLib.MAXUINT) + assert v.get_value() == GLib.MAXUINT + + +def test_value_gtype(): + class TestObject(GObject.GObject): + pass + + v = GObject.Value(GObject.TYPE_GTYPE) + assert v.get_value() == GObject.TYPE_INVALID + v.set_value(TestObject.__gtype__) + assert v.get_value() == TestObject.__gtype__ + v.set_value(TestObject) + assert v.get_value() == TestObject.__gtype__ + + with pytest.raises(TypeError): + v.set_value(None) + + +def test_value_variant(): + v = GObject.Value(GObject.TYPE_VARIANT) + assert v.get_value() is None + variant = GLib.Variant('i', 42) + v.set_value(variant) + assert v.get_value() == variant + + v.set_value(None) + assert v.get_value() is None + + with pytest.raises(TypeError): + v.set_value(object()) + + +def test_value_param(): + # FIXME: set_value and get_value trigger a critical + # GObject.Value(GObject.TYPE_PARAM) + pass + + +def test_value_string(): + v = GObject.Value(GObject.TYPE_STRING) + assert v.get_value() is None + + if PY2: + v.set_value(b"bar") + assert v.get_value() == b"bar" + + v.set_value(u"öäü") + assert v.get_value().decode("utf-8") == u"öäü" + else: + with pytest.raises(TypeError): + v.set_value(b"bar") + + v.set_value(u"quux") + assert v.get_value() == u"quux" + assert isinstance(v.get_value(), str) + + with pytest.raises(TypeError): + v.set_value(None) + + +def test_value_pyobject(): + class Foo(object): + pass + + v = GObject.Value(GObject.TYPE_PYOBJECT) + assert v.get_value() is None + for obj in [Foo(), None, 42, "foo"]: + v.set_value(obj) + assert v.get_value() == obj + + +@ignore_gi_deprecation_warnings +def test_value_char(): + v = GObject.Value(GObject.TYPE_CHAR) + assert v.get_value() == 0 + v.set_value(42) + assert v.get_value() == 42 + v.set_value(-1) + assert v.get_value() == -1 + v.set_value(b"a") + assert v.get_value() == 97 + v.set_value(b"\x00") + assert v.get_value() == 0 + + with pytest.raises(TypeError): + v.set_value(u"a") + + with pytest.raises(OverflowError): + v.set_value(128) + + +def test_value_uchar(): + v = GObject.Value(GObject.TYPE_UCHAR) + assert v.get_value() == 0 + v.set_value(200) + assert v.get_value() == 200 + v.set_value(b"a") + assert v.get_value() == 97 + v.set_value(b"\x00") + assert v.get_value() == 0 + + with pytest.raises(TypeError): + v.set_value(u"a") + + with pytest.raises(OverflowError): + v.set_value(256) diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py index 887a4f3..99406c6 100644 --- a/tests/test_overrides_gtk.py +++ b/tests/test_overrides_gtk.py @@ -11,10 +11,13 @@ import sys import gc import warnings +import pytest + from .helper import ignore_gi_deprecation_warnings, capture_glib_warnings import gi.overrides import gi.types +from gi._compat import cmp from gi.repository import GLib, GObject try: @@ -104,6 +107,24 @@ def test_freeze_child_notif(): assert events.count("pack-type") == 1 +@unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +def test_menu_popup(): + m = Gtk.Menu() + with capture_glib_warnings(): + m.popup(None, None, None, None, 0, 0) + m.popdown() + + +@unittest.skipUnless(Gtk, 'Gtk not available') +@unittest.skipIf(Gtk_version == "4.0", "not in gtk4") +def test_button_stock(): + with capture_glib_warnings(): + button = Gtk.Button(stock=Gtk.STOCK_OK) + assert button.props.label == Gtk.STOCK_OK + assert button.props.use_stock + + @unittest.skipUnless(Gtk, 'Gtk not available') def test_wrapper_toggle_refs(): class MyButton(Gtk.Button): @@ -215,6 +236,53 @@ class TestGtk(unittest.TestCase): expected_results.remove(a) action.activate() + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_action_group_error_handling(self): + action_group = Gtk.ActionGroup(name='TestActionGroup') + with pytest.raises(TypeError): + action_group.add_actions(42) + + with pytest.raises(TypeError): + action_group.add_toggle_actions(42) + + with pytest.raises(TypeError): + action_group.add_radio_actions(42) + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_action_group_no_user_data(self): + action_group = Gtk.ActionGroup(name='TestActionGroup') + + called = [] + + def test_action_callback_no_data(action): + called.append(action) + + action_group.add_actions([ + ('test-action1', None, 'Test Action 1', + None, None, test_action_callback_no_data)]) + action_group.add_actions([('test2-action1',)]) + action_group.get_action('test-action1').activate() + + action_group.add_toggle_actions([ + ('test-action2', None, 'Test Action 2', + None, None, test_action_callback_no_data)]) + action_group.add_toggle_actions([('test2-action2',)]) + action_group.get_action('test-action2').activate() + + def test_action_callback_no_data_radio(action, current): + called.append(action) + + action_group.add_radio_actions([ + ('test-action3', None, 'Test Action 3', None, None, 0), + ('test-action4', None, 'Test Action 4', None, None, 1)], + 1, test_action_callback_no_data_radio) + action_group.add_radio_actions([('test2-action3',)]) + action = action_group.get_action('test-action3') + assert action.get_current_value() == 1 + action.activate() + + assert len(called) == 3 + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_uimanager(self): self.assertEqual(Gtk.UIManager, gi.overrides.Gtk.UIManager) @@ -222,8 +290,7 @@ class TestGtk(unittest.TestCase): ui.add_ui_from_string(""" -""" -) +""") menubar = ui.get_widget("/menubar1") self.assertEqual(type(menubar), Gtk.MenuBar) @@ -235,6 +302,9 @@ class TestGtk(unittest.TestCase): self.assertEqual(ag, groups[-2]) self.assertEqual(ag2, groups[-1]) + with pytest.raises(TypeError): + ui.add_ui_from_string(42) + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") def test_uimanager_nonascii(self): ui = Gtk.UIManager() @@ -356,6 +426,15 @@ class TestGtk(unittest.TestCase): button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE) self.assertEqual('gtk-close', button.get_label()) + with pytest.raises(ValueError, match="even number"): + dialog.add_buttons('test-button2', 2, 'gtk-close') + + @unittest.skipIf(Gtk_version == "4.0", "not in gtk4") + def test_dialog_deprecated_attributes(self): + dialog = Gtk.Dialog() + assert dialog.action_area == dialog.get_action_area() + assert dialog.vbox == dialog.get_content_area() + def test_about_dialog(self): dialog = Gtk.AboutDialog() self.assertTrue(isinstance(dialog, Gtk.Dialog)) @@ -717,6 +796,11 @@ class TestGtk(unittest.TestCase): pixbuf = GdkPixbuf.Pixbuf() Gtk.IconSet.new_from_pixbuf(pixbuf) + with warnings.catch_warnings(record=True) as warn: + warnings.simplefilter('always') + Gtk.IconSet(pixbuf) + assert issubclass(warn[0].category, PyGTKDeprecationWarning) + def test_viewport(self): vadjustment = Gtk.Adjustment() hadjustment = Gtk.Adjustment() @@ -853,8 +937,15 @@ class TestBuilder(unittest.TestCase): []), } + class SignalTestObject(GObject.GObject): + __gtype_name__ = "GIOverrideSignalTestObject" + def test_add_from_string(self): builder = Gtk.Builder() + + with pytest.raises(TypeError): + builder.add_from_string(object()) + builder.add_from_string(u"") builder.add_from_string("") @@ -877,6 +968,9 @@ class TestBuilder(unittest.TestCase): builder.add_objects_from_string("", ['']) builder.add_objects_from_string(get_example(u"ä" * 1000), ['']) + with pytest.raises(TypeError): + builder.add_objects_from_string(object(), []) + def test_extract_handler_and_args_object(self): class Obj(): pass @@ -908,6 +1002,13 @@ class TestBuilder(unittest.TestCase): Gtk._extract_handler_and_args, obj, 'not_a_handler') + def test_extract_handler_and_args_type_mismatch(self): + with pytest.raises(TypeError): + Gtk._extract_handler_and_args({"foo": object()}, "foo") + + with pytest.raises(TypeError): + Gtk._extract_handler_and_args({"foo": []}, "foo") + def test_builder_with_handler_and_args(self): builder = Gtk.Builder() builder.add_from_string(""" @@ -936,6 +1037,31 @@ class TestBuilder(unittest.TestCase): self.assertSequenceEqual(args_collector[0], (obj, 1, 2)) self.assertSequenceEqual(args_collector[1], (obj, )) + def test_builder_with_handler_object(self): + builder = Gtk.Builder() + builder.add_from_string(""" + + + + + + + + """) + + args_collector = [] + + def on_signal(*args): + args_collector.append(args) + + builder.connect_signals({'on_signal1': (on_signal, 1, 2), + 'on_signal2': on_signal}) + obj, emitter = builder.get_objects() + emitter.emit("test-signal") + assert len(args_collector) == 2 + assert args_collector[0] == (obj, 1, 2) + assert args_collector[1] == (obj, ) + def test_builder(self): self.assertEqual(Gtk.Builder, gi.overrides.Gtk.Builder) @@ -996,6 +1122,31 @@ class TestBuilder(unittest.TestCase): self.assertEqual(signal_checker.after_sentinel, 2) +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestTreeModelRow(unittest.TestCase): + def test_tree_model_row(self): + model = Gtk.TreeStore(int) + iter_ = model.append(None, [42]) + path = model.get_path(iter_) + Gtk.TreeModelRow(model, iter_) + row = Gtk.TreeModelRow(model, path) + + with pytest.raises(TypeError): + row["foo"] + + with pytest.raises(TypeError): + Gtk.TreeModelRow(model, 42) + + with pytest.raises(TypeError): + Gtk.TreeModelRow(42, path) + + iter_ = model.append(None, [24]) + row = Gtk.TreeModelRow(model, iter_) + assert row.previous[0] == 42 + assert row.get_previous()[0] == 42 + assert row.previous.previous is None + + @ignore_gi_deprecation_warnings @unittest.skipUnless(Gtk, 'Gtk not available') class TestTreeModel(unittest.TestCase): @@ -1627,6 +1778,26 @@ class TestTreeModel(unittest.TestCase): ([0], [-1]), ([0, 0], [0]), ([0, 1], [None]), ([0, 2], [None]), ([0, 3], [4321]), ([0, 4], [1234])] + def test_tree_store_insert_before_none(self): + store = Gtk.TreeStore(object) + root = store.append(None) + sub = store.append(root) + + iter_ = store.insert_before(None, None, [1]) + assert store.get_path(iter_).get_indices() == [1] + + iter_ = store.insert_before(root, None, [1]) + assert store.get_path(iter_).get_indices() == [0, 1] + + iter_ = store.insert_before(sub, None, [1]) + assert store.get_path(iter_).get_indices() == [0, 0, 0] + + iter_ = store.insert_before(None, root, [1]) + assert store.get_path(iter_).get_indices() == [0] + + iter_ = store.insert_before(None, sub, [1]) + assert store.get_path(iter_).get_indices() == [1, 0] + def test_tree_store_insert_after(self): store = Gtk.TreeStore(object) signals = [] @@ -1688,6 +1859,26 @@ class TestTreeModel(unittest.TestCase): ([0], [-1]), ([0, 0], [1234]), ([0, 1], [4321]), ([0, 2], [None]), ([0, 3], [None]), ([0, 4], [0])] + def test_tree_store_insert_after_none(self): + store = Gtk.TreeStore(object) + root = store.append(None) + sub = store.append(root) + + iter_ = store.insert_after(None, None, [1]) + assert store.get_path(iter_).get_indices() == [0] + + iter_ = store.insert_after(root, None, [1]) + assert store.get_path(iter_).get_indices() == [1, 0] + + iter_ = store.insert_after(sub, None, [1]) + assert store.get_path(iter_).get_indices() == [1, 1, 0] + + iter_ = store.insert_after(None, root, [1]) + assert store.get_path(iter_).get_indices() == [2] + + iter_ = store.insert_after(None, sub, [1]) + assert store.get_path(iter_).get_indices() == [1, 2] + def test_tree_path(self): p1 = Gtk.TreePath() p2 = Gtk.TreePath.new_first() @@ -2056,6 +2247,14 @@ class TestTreeModel(unittest.TestCase): filtered[0][1] = 'ONE' self.assertEqual(filtered[0][1], 'ONE') + def foo(store, iter_, data): + assert data is None + return False + + filtered.set_visible_func(foo) + filtered.refilter() + assert len(filtered) == 0 + def test_list_store_performance(self): model = Gtk.ListStore(int, str) @@ -2074,6 +2273,89 @@ class TestTreeModel(unittest.TestCase): filt = model.filter_new() self.assertTrue(filt is not None) + def test_tree_store_set(self): + tree_store = Gtk.TreeStore(int, int) + iter_ = tree_store.append(None) + tree_store.set(iter_) + assert tree_store.get_value(iter_, 0) == 0 + tree_store.set(iter_, 0, 42) + assert tree_store.get_value(iter_, 0) == 42 + assert tree_store.get_value(iter_, 1) == 0 + tree_store.set(iter_, 0, 42, 1, 24) + assert tree_store.get_value(iter_, 1) == 24 + tree_store.set(iter_, 1, 124) + assert tree_store.get_value(iter_, 1) == 124 + + with pytest.raises(TypeError): + tree_store.set(iter_, 0, 42, "foo", 24) + with pytest.raises(TypeError): + tree_store.set(iter_, "foo") + with pytest.raises(TypeError): + tree_store.set(iter_, [1, 2, 3]) + with pytest.raises(TypeError): + tree_store.set(iter_, 0, 42, 24) + + def test_list_store_set(self): + list_store = Gtk.ListStore(int, int) + iter_ = list_store.append() + list_store.set(iter_) + assert list_store.get_value(iter_, 0) == 0 + list_store.set(iter_, 0, 42) + assert list_store.get_value(iter_, 0) == 42 + assert list_store.get_value(iter_, 1) == 0 + list_store.set(iter_, 0, 42, 1, 24) + assert list_store.get_value(iter_, 1) == 24 + list_store.set(iter_, 1, 124) + assert list_store.get_value(iter_, 1) == 124 + + with pytest.raises(TypeError): + list_store.set(iter_, 0, 42, "foo", 24) + with pytest.raises(TypeError): + list_store.set(iter_, "foo") + with pytest.raises(TypeError): + list_store.set(iter_, [1, 2, 3]) + with pytest.raises(TypeError): + list_store.set(iter_, 0, 42, 24) + + def test_set_default_sort_func(self): + list_store = Gtk.ListStore(int) + list_store.append([2]) + list_store.append([1]) + + def sort_func(store, iter1, iter2, data): + assert data is None + return cmp(store[iter1][0], store[iter2][0]) + + list_store.set_default_sort_func(sort_func) + list_store.set_sort_column_id( + Gtk.TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, Gtk.SortType.ASCENDING) + assert [v[0] for v in list_store] == [1, 2] + + def test_model_rows_reordered(self): + list_store = Gtk.ListStore(int) + list_store.append([2]) + list_store.append([1]) + list_store.rows_reordered(Gtk.TreePath.new_first(), None, [0, 1]) + list_store.rows_reordered(0, None, [0, 1]) + + def test_model_set_row(self): + list_store = Gtk.ListStore(int, int) + list_store.append([1, 2]) + iter_ = list_store.get_iter_first() + list_store.set_row(iter_, [3, 4]) + assert list_store[0][:] == [3, 4] + list_store.set_row(iter_, [None, 7]) + assert list_store[0][:] == [3, 7] + list_store.set_row(iter_, [None, GObject.Value(int, 9)]) + assert list_store[0][:] == [3, 9] + + def test_model_set_row_skip_on_none(self): + list_store = Gtk.ListStore(int, int, int, int) + list_store.append([1, 2, 3, 4]) + iter_ = list_store.get_iter_first() + list_store.set_row(iter_, [None, 7, None, 9]) + assert list_store[0][:] == [1, 7, 3, 9] + @unittest.skipIf(sys.platform == "darwin", "hangs") @unittest.skipUnless(Gtk, 'Gtk not available') @@ -2184,12 +2466,31 @@ class TestTreeView(unittest.TestCase): self.assertEqual(m, store) self.assertEqual(store.get_path(s), firstpath) + sel.unselect_all() + (m, s) = sel.get_selected() + assert s is None + + sel.select_path(0) + m, r = sel.get_selected_rows() + assert m == store + assert len(r) == 1 + assert r[0] == store[0].path + + def test_scroll_to_cell(self): + store = Gtk.ListStore(int, str) + store.append((0, "foo")) + view = Gtk.TreeView() + view.set_model(store) + view.scroll_to_cell(store[0].path) + view.scroll_to_cell(0) + @unittest.skipUnless(Gtk, 'Gtk not available') class TestTextBuffer(unittest.TestCase): def test_text_buffer(self): self.assertEqual(Gtk.TextBuffer, gi.overrides.Gtk.TextBuffer) buffer = Gtk.TextBuffer() + assert buffer.get_tag_table() is not None tag = buffer.create_tag('title', font='Sans 18') self.assertEqual(tag.props.name, 'title') @@ -2261,6 +2562,15 @@ class TestTextBuffer(unittest.TestCase): self.assertRaises(ValueError, buffer.insert_with_tags_by_name, buffer.get_start_iter(), 'HelloHello', 'unknowntag') + def test_insert(self): + buffer = Gtk.TextBuffer() + start = buffer.get_bounds()[0] + with pytest.raises(TypeError): + buffer.insert(start, 42) + + with pytest.raises(TypeError): + buffer.insert_at_cursor(42) + def test_text_iter(self): try: starts_tag = Gtk.TextIter.starts_tag @@ -2336,6 +2646,18 @@ class TestTextBuffer(unittest.TestCase): self.assertEqual(values, [u"c", u"b", u"a"]) +@unittest.skipUnless(Gtk, 'Gtk not available') +class TestPaned(unittest.TestCase): + + def test_pack_defaults(self): + p = Gtk.Paned() + l1 = Gtk.Label() + l2 = Gtk.Label() + p.pack1(l1) + p.pack2(l2) + assert p.get_children() == [l1, l2] + + @unittest.skipUnless(Gtk, 'Gtk not available') class TestContainer(unittest.TestCase): diff --git a/tests/test_repository.py b/tests/test_repository.py index d324dfc..2da7f5a 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -23,7 +23,10 @@ from __future__ import absolute_import import unittest -import collections +try: + from collections import abc +except ImportError: + import collections as abc import gi._gi as GIRepository from gi.module import repository as repo @@ -113,12 +116,12 @@ class Test(unittest.TestCase): def test_object_info(self): info = repo.find_by_name('GIMarshallingTests', 'Object') self.assertEqual(info.get_parent(), repo.find_by_name('GObject', 'Object')) - self.assertTrue(isinstance(info.get_methods(), collections.Iterable)) - self.assertTrue(isinstance(info.get_fields(), collections.Iterable)) - self.assertTrue(isinstance(info.get_interfaces(), collections.Iterable)) - self.assertTrue(isinstance(info.get_constants(), collections.Iterable)) - self.assertTrue(isinstance(info.get_vfuncs(), collections.Iterable)) - self.assertTrue(isinstance(info.get_properties(), collections.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_interfaces(), abc.Iterable)) + self.assertTrue(isinstance(info.get_constants(), abc.Iterable)) + self.assertTrue(isinstance(info.get_vfuncs(), abc.Iterable)) + self.assertTrue(isinstance(info.get_properties(), abc.Iterable)) self.assertFalse(info.get_abstract()) self.assertEqual(info.get_class_struct(), repo.find_by_name('GIMarshallingTests', 'ObjectClass')) self.assertEqual(info.get_type_name(), 'GIMarshallingTestsObject') @@ -158,12 +161,12 @@ class Test(unittest.TestCase): def test_interface_info(self): info = repo.find_by_name('GIMarshallingTests', 'Interface') - self.assertTrue(isinstance(info.get_methods(), collections.Iterable)) - self.assertTrue(isinstance(info.get_vfuncs(), collections.Iterable)) - self.assertTrue(isinstance(info.get_constants(), collections.Iterable)) - self.assertTrue(isinstance(info.get_prerequisites(), collections.Iterable)) - self.assertTrue(isinstance(info.get_properties(), collections.Iterable)) - self.assertTrue(isinstance(info.get_signals(), collections.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) + self.assertTrue(isinstance(info.get_vfuncs(), abc.Iterable)) + self.assertTrue(isinstance(info.get_constants(), abc.Iterable)) + self.assertTrue(isinstance(info.get_prerequisites(), abc.Iterable)) + self.assertTrue(isinstance(info.get_properties(), abc.Iterable)) + self.assertTrue(isinstance(info.get_signals(), abc.Iterable)) method = info.find_method('test_int8_in') vfunc = info.find_vfunc('test_int8_in') @@ -179,27 +182,35 @@ class Test(unittest.TestCase): def test_struct_info(self): info = repo.find_by_name('GIMarshallingTests', 'InterfaceIface') self.assertTrue(isinstance(info, GIRepository.StructInfo)) - self.assertTrue(isinstance(info.get_fields(), collections.Iterable)) - self.assertTrue(isinstance(info.get_methods(), collections.Iterable)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) self.assertTrue(isinstance(info.get_size(), int)) self.assertTrue(isinstance(info.get_alignment(), int)) self.assertTrue(info.is_gtype_struct()) self.assertFalse(info.is_foreign()) + info = repo.find_by_name('GIMarshallingTests', 'SimpleStruct') + assert info.find_method("nope") is None + assert isinstance(info.find_method("method"), GIRepository.FunctionInfo) + + assert info.find_field("nope") is None + assert isinstance(info.find_field("int8"), GIRepository.FieldInfo) + def test_enum_info(self): info = repo.find_by_name('GIMarshallingTests', 'Enum') self.assertTrue(isinstance(info, GIRepository.EnumInfo)) - self.assertTrue(isinstance(info.get_values(), collections.Iterable)) - self.assertTrue(isinstance(info.get_methods(), collections.Iterable)) + self.assertTrue(isinstance(info.get_values(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) self.assertFalse(info.is_flags()) self.assertTrue(info.get_storage_type() > 0) # might be platform dependent def test_union_info(self): info = repo.find_by_name('GIMarshallingTests', 'Union') self.assertTrue(isinstance(info, GIRepository.UnionInfo)) - self.assertTrue(isinstance(info.get_fields(), collections.Iterable)) - self.assertTrue(isinstance(info.get_methods(), collections.Iterable)) + self.assertTrue(isinstance(info.get_fields(), abc.Iterable)) + self.assertTrue(isinstance(info.get_methods(), abc.Iterable)) self.assertTrue(isinstance(info.get_size(), int)) + self.assertTrue(isinstance(info.get_alignment(), int)) def test_type_info(self): func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct') @@ -245,7 +256,7 @@ class Test(unittest.TestCase): def test_callable_info(self): func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct') self.assertTrue(hasattr(func_info, 'invoke')) - self.assertTrue(isinstance(func_info.get_arguments(), collections.Iterable)) + self.assertTrue(isinstance(func_info.get_arguments(), abc.Iterable)) self.assertEqual(func_info.get_caller_owns(), GIRepository.Transfer.NOTHING) self.assertFalse(func_info.may_return_null()) self.assertEqual(func_info.get_return_type().get_tag(), GIRepository.TypeTag.VOID) diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py index 3ffdf93..bd1056e 100644 --- a/tests/test_subprocess.py +++ b/tests/test_subprocess.py @@ -7,9 +7,34 @@ import os import unittest import warnings +import pytest + from gi.repository import GLib from gi import PyGIDeprecationWarning +from .helper import capture_gi_deprecation_warnings + + +def test_child_watch_add_get_args_various(): + cb = lambda pid, status: None + get_args = GLib._child_watch_add_get_args + pid = 42 + with capture_gi_deprecation_warnings(): + assert get_args(pid, cb, 2) == (0, pid, cb, (2,)) + + with pytest.raises(TypeError): + get_args(pid, cb, 2, 3, 4) + + assert get_args(0, pid, 2, 3, function=cb) == (0, pid, cb, (2, 3)) + assert get_args(0, pid, cb, 2, 3) == (0, pid, cb, (2, 3)) + assert get_args(0, pid, cb, data=99) == (0, pid, cb, (99,)) + + with pytest.raises(TypeError): + get_args(0, pid, 24) + + with pytest.raises(TypeError): + get_args(0, pid, cb, 2, 3, data=99) + @unittest.skipIf(os.name == "nt", "not on Windows") class TestProcess(unittest.TestCase): -- 2.34.1