summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml6
-rwxr-xr-x.gitlab-ci/test-docker-gtk4.sh10
-rwxr-xr-x.gitlab-ci/test-docker-old.sh10
-rwxr-xr-x.gitlab-ci/test-flatpak.sh4
-rw-r--r--gi/overrides/Gtk.py50
-rw-r--r--tests/test_overrides_gtk.py286
6 files changed, 320 insertions, 46 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a1a569b7..e00e1343 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -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
+ artifacts:
+ paths:
+ - coverage/
script:
- bash -x ./.gitlab-ci/test-docker-gtk4.sh
diff --git a/.gitlab-ci/test-docker-gtk4.sh b/.gitlab-ci/test-docker-gtk4.sh
index e36c715d..0dd12199 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 74c81c77..91312c7c 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 3e3a9923..3ca5a746 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/gi/overrides/Gtk.py b/gi/overrides/Gtk.py
index 7e00c611..d9c1a05f 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):
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
index 887a4f3d..ddd652e3 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:
@@ -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):
@@ -2056,6 +2207,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 +2233,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 +2426,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 +2522,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 +2607,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")