Imported Upstream version 3.31.2 upstream/3.31.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 25 Nov 2020 05:48:13 +0000 (14:48 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 25 Nov 2020 05:48:13 +0000 (14:48 +0900)
31 files changed:
.gitlab-ci.yml
.gitlab-ci/Dockerfile
.gitlab-ci/Dockerfile.gtk4
.gitlab-ci/run-docker-gtk4.sh
.gitlab-ci/run-docker.sh
.gitlab-ci/test-docker-gtk4.sh
.gitlab-ci/test-docker-old.sh
.gitlab-ci/test-flatpak.sh
NEWS
PKG-INFO.in
gi/gimodule.c
gi/overrides/GLib.py
gi/overrides/GObject.py
gi/overrides/Gdk.py
gi/overrides/Gtk.py
gi/pygi-basictype.c
gi/pygi-foreign-cairo.c
gi/pygi-info.c
meson.build
pygobject.doap
setup.py
tests/test_cairo.py
tests/test_gdbus.py
tests/test_generictreemodel.py
tests/test_gobject.py
tests/test_overrides_gdk.py
tests/test_overrides_glib.py
tests/test_overrides_gobject.py [new file with mode: 0644]
tests/test_overrides_gtk.py
tests/test_repository.py
tests/test_subprocess.py

index a1a569b..c7d6633 100644 (file)
@@ -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
 
index a72c718..48e2960 100644 (file)
@@ -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
 
index 297a70d..6420e66 100644 (file)
@@ -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
index f6c979d..8bd0a16 100755 (executable)
@@ -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 \
index bcfd707..fe0cfc4 100755 (executable)
@@ -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
index e36c715..0dd1219 100755 (executable)
@@ -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
index 74c81c7..91312c7 100755 (executable)
@@ -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
index 3e3a992..3ca5a74 100755 (executable)
@@ -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 (file)
--- 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 <tmiasko>`)
+
+
+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 <cianwilson>`
+  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
 -------------------
 
index c357cc3..34891d7 100644 (file)
@@ -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
index 3a61616..7ff9b0e 100644 (file)
@@ -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);
index d717d5f..e0ac233 100644 (file)
@@ -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
index 4a6102d..a89ddc7 100644 (file)
@@ -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 '<Value (%s) %s>' % (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
index 44914db..5aea65f 100644 (file)
@@ -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 '<Gdk.Atom(%i)>' % 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 '<Gdk.Atom(%i)>' % 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)
index 7e00c61..ba0a71d 100644 (file)
@@ -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)
 
index 82b43c5..dafddf2 100644 (file)
@@ -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;
     }
index 718f9a0..c99847b 100644 (file)
@@ -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);
index 4fca825..5508001 100644 (file)
@@ -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 }
 };
 
index db9bca3..5271ce9 100644 (file)
@@ -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'
index 63f184f..cca2a48 100644 (file)
@@ -19,7 +19,7 @@ PyGObject now dynamically accesses any GObject libraries that uses GObject Intro
   <mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/python-hackers-list" />
   <category rdf:resource="http://api.gnome.org/doap-extensions#core" />
   <download-page rdf:resource="http://download.gnome.org/sources/pygobject/" />
-  <bug-database rdf:resource="https://bugzilla.gnome.org/browse.cgi?product=pygobject" />
+  <bug-database rdf:resource="https://gitlab.gnome.org/GNOME/pygobject/issues/" />
   <programming-language>C</programming-language>
   <programming-language>Python</programming-language>
   <maintainer>
index b4aa14c..f9d5ce6 100755 (executable)
--- 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)
index 8ccbe15..f4d0d7c 100644 (file)
@@ -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))
index 18315af..6e38abe 100644 (file)
@@ -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()
index 5e9d716..acb6028 100644 (file)
@@ -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)
 
index bef3e9b..9b4656b 100644 (file)
@@ -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)
index 9c36674..88fc465 100644 (file)
@@ -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),
                          '<flags GDK_META_MASK | GDK_RELEASE_MASK of type Gdk.ModifierType>')
 
-    @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"<Gdk.Atom\(\d+\)>", repr(atom))
+        assert re.match(r"Gdk.Atom<\d+>", str(atom))
index 3467253..ac19ccd 100644 (file)
 
 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 (file)
index 0000000..37292db
--- /dev/null
@@ -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) == "<Value (invalid) None>"
+
+    v = GObject.Value(int, 0)
+    assert repr(v) == "<Value (gint) 0>"
+
+
+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)
index 887a4f3..99406c6 100644 (file)
@@ -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:
@@ -105,6 +108,24 @@ def test_freeze_child_notif():
 
 
 @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):
         def __init__(self, height):
@@ -216,14 +237,60 @@ class TestGtk(unittest.TestCase):
             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)
         ui = Gtk.UIManager()
         ui.add_ui_from_string("""<ui>
     <menubar name="menubar1"></menubar>
 </ui>
-"""
-)
+""")
         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("""
+            <interface>
+              <object class="GIOverrideSignalTestObject" id="foo"/>
+              <object class="GIOverrideSignalTest" id="object_sig_test">
+                  <signal name="test-signal" handler="on_signal1" object="foo"/>
+                  <signal name="test-signal" handler="on_signal2" after="yes" object="foo" />
+              </object>
+            </interface>
+            """)
+
+        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
@@ -2337,6 +2647,18 @@ class TestTextBuffer(unittest.TestCase):
 
 
 @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):
 
     @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
index d324dfc..2da7f5a 100644 (file)
 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)
index 3ffdf93..bd1056e 100644 (file)
@@ -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):