Skip to content

Commit 3cd7e20

Browse files
authored
Track in-flight changes in model tester (#421)
Relevant upstream changes: Add tests to QAbstractItemModelTester checking only one change in flight https://codereview.qt-project.org/c/qt/qtbase/+/396208 QAbstractItemModelTester: Fix typos in debug output https://codereview.qt-project.org/c/qt/qtbase/+/408233
1 parent b3fb5ee commit 3cd7e20

File tree

2 files changed

+188
-1
lines changed

2 files changed

+188
-1
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ UNRELEASED
77
- New ``qapp_cls`` fixture returning the ``QApplication`` class to use, thus
88
making it easier to use a custom subclass without having to override the
99
whole ``qapp`` fixture. Thanks `@The-Compiler`_ for the PR.
10+
- Updated model tester to track/verify in-flight changes based on Qt's updates.
1011

1112
.. _#383: https://github.com/pytest-dev/pytest-qt/pull/383
1213
.. _@luziferius: https://github.com/luziferius

src/pytestqt/modeltest.py

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This file is based on the original C++ qabstractitemmodeltester.cpp from:
22
# http://code.qt.io/cgit/qt/qtbase.git/tree/src/testlib/qabstractitemmodeltester.cpp
3-
# Commit 1f2f61d80860f55638cfd194bbed5d679a588b1f
3+
# Commit b6759ff81c1b6ecb7e18144db0b7c9c5884d7f24
44
#
55
# Licensed under the following terms:
66
#
@@ -41,6 +41,7 @@
4141
#
4242
# $QT_END_LICENSE$
4343

44+
import enum
4445
import collections
4546

4647
from pytestqt.qt_compat import qt_api
@@ -52,6 +53,18 @@
5253
HAS_QT_TESTER = hasattr(qt_api.QtTest, "QAbstractItemModelTester")
5354

5455

56+
class _ChangeInFlight(enum.Enum):
57+
58+
COLUMNS_INSERTED = enum.auto()
59+
COLUMNS_MOVED = enum.auto()
60+
COLUMNS_REMOVED = enum.auto()
61+
LAYOUT_CHANGED = enum.auto()
62+
MODEL_RESET = enum.auto()
63+
ROWS_INSERTED = enum.auto()
64+
ROWS_MOVED = enum.auto()
65+
ROWS_REMOVED = enum.auto()
66+
67+
5568
class ModelTester:
5669

5770
"""A tester for Qt's QAbstractItemModels."""
@@ -63,6 +76,7 @@ def __init__(self, config):
6376
self._remove = None
6477
self._changing = []
6578
self._qt_tester = None
79+
self._change_in_flight = None
6680

6781
def _debug(self, text):
6882
print("modeltest: " + text)
@@ -131,10 +145,32 @@ def check(self, model, force_py=False):
131145
# Special checks for changes
132146
self._model.layoutAboutToBeChanged.connect(self._on_layout_about_to_be_changed)
133147
self._model.layoutChanged.connect(self._on_layout_changed)
148+
149+
# column operations
150+
self._model.columnsAboutToBeInserted.connect(
151+
self._on_columns_about_to_be_inserted
152+
)
153+
self._model.columnsAboutToBeMoved.connect(self._on_columns_about_to_be_moved)
154+
self._model.columnsAboutToBeRemoved.connect(
155+
self._on_columns_about_to_be_removed
156+
)
157+
self._model.columnsInserted.connect(self._on_columns_inserted)
158+
self._model.columnsMoved.connect(self._on_columns_moved)
159+
self._model.columnsRemoved.connect(self._on_columns_removed)
160+
161+
# row operations
134162
self._model.rowsAboutToBeInserted.connect(self._on_rows_about_to_be_inserted)
163+
self._model.rowsAboutToBeMoved.connect(self._on_rows_about_to_be_moved)
135164
self._model.rowsAboutToBeRemoved.connect(self._on_rows_about_to_be_removed)
136165
self._model.rowsInserted.connect(self._on_rows_inserted)
166+
self._model.rowsMoved.connect(self._on_rows_moved)
137167
self._model.rowsRemoved.connect(self._on_rows_removed)
168+
169+
# reset
170+
self._model.modelAboutToBeReset.connect(self._on_model_about_to_be_reset)
171+
self._model.modelReset.connect(self._on_model_reset)
172+
173+
# data
138174
self._model.dataChanged.connect(self._on_data_changed)
139175
self._model.headerDataChanged.connect(self._on_header_data_changed)
140176

@@ -515,11 +551,104 @@ def _test_data(self):
515551
qt_api.QtCore.Qt.CheckState.Checked,
516552
]
517553

554+
def _on_columns_about_to_be_inserted(self, parent, start, end):
555+
assert self._change_in_flight is None
556+
self._change_in_flight = _ChangeInFlight.COLUMNS_INSERTED
557+
last_index = self._model.index(start - 1, 0, parent)
558+
self._debug(
559+
"columns about to be inserted: start {}, end {}, parent {}, "
560+
"current count of parent {}, last before insertion {} {}".format(
561+
start,
562+
end,
563+
self._modelindex_debug(parent),
564+
self._model.rowCount(parent),
565+
self._modelindex_debug(last_index),
566+
self._model.data(last_index),
567+
)
568+
)
569+
570+
def _on_columns_inserted(self, parent, start, end):
571+
assert self._change_in_flight == _ChangeInFlight.COLUMNS_INSERTED
572+
self._change_in_flight = None
573+
self._debug(
574+
"columns inserted: start {}, end {}, parent {}, "
575+
"current count of parent {}, ".format(
576+
start,
577+
end,
578+
self._modelindex_debug(parent),
579+
self._model.rowCount(parent),
580+
)
581+
)
582+
583+
def _on_columns_about_to_be_moved(
584+
self, source_parent, source_start, source_end, dest_parent, dest_column
585+
):
586+
assert self._change_in_flight is None
587+
self._change_in_flight = _ChangeInFlight.COLUMNS_MOVED
588+
self._debug(
589+
"columns about to be moved: source start {}, source end {}, "
590+
"source parent {}, destination parent {}, "
591+
"destination column {}".format(
592+
source_start,
593+
source_end,
594+
self._modelindex_debug(source_parent),
595+
self._modelindex_debug(dest_parent),
596+
dest_column,
597+
)
598+
)
599+
600+
def _on_columns_moved(
601+
self, source_parent, source_start, source_end, dest_parent, dest_column
602+
):
603+
assert self._change_in_flight == _ChangeInFlight.COLUMNS_MOVED
604+
self._change_in_flight = None
605+
self._debug(
606+
"columns moved: source start {}, source end {}, "
607+
"source parent {}, destination parent {}, "
608+
"destination column {}".format(
609+
source_start,
610+
source_end,
611+
self._modelindex_debug(source_parent),
612+
self._modelindex_debug(dest_parent),
613+
dest_column,
614+
)
615+
)
616+
617+
def _on_columns_about_to_be_removed(self, parent, start, end):
618+
assert self._change_in_flight is None
619+
self._change_in_flight = _ChangeInFlight.COLUMNS_REMOVED
620+
last_index = self._model.index(start - 1, 0, parent)
621+
self._debug(
622+
"columns about to be removed: start {}, end {}, "
623+
"parent {}, parent rowcount {}, last before removal {}".format(
624+
start,
625+
end,
626+
self._modelindex_debug(parent),
627+
self._model.rowCount(parent),
628+
self._modelindex_debug(last_index),
629+
)
630+
)
631+
632+
def _on_columns_removed(self, parent, start, end):
633+
assert self._change_in_flight == _ChangeInFlight.COLUMNS_REMOVED
634+
self._change_in_flight = None
635+
self._debug(
636+
"columns removed: start {}, end {}, parent {}, parent rowcount {}".format(
637+
start,
638+
end,
639+
self._modelindex_debug(parent),
640+
self._model.rowCount(parent),
641+
)
642+
)
643+
518644
def _on_rows_about_to_be_inserted(self, parent, start, end):
519645
"""Store what is about to be inserted.
520646
521647
This gets stored to make sure it actually happens in rowsInserted.
522648
"""
649+
assert self._change_in_flight is None
650+
self._change_in_flight = _ChangeInFlight.ROWS_INSERTED
651+
523652
last_index = self._model.index(start - 1, 0, parent)
524653
next_index = self._model.index(start, 0, parent)
525654
parent_rowcount = self._model.rowCount(parent)
@@ -545,6 +674,9 @@ def _on_rows_about_to_be_inserted(self, parent, start, end):
545674

546675
def _on_rows_inserted(self, parent, start, end):
547676
"""Confirm that what was said was going to happen actually did."""
677+
assert self._change_in_flight == _ChangeInFlight.ROWS_INSERTED
678+
self._change_in_flight = None
679+
548680
c = self._insert.pop()
549681
last_data = (
550682
self._model.data(self._model.index(start - 1, 0, parent))
@@ -595,21 +727,72 @@ def _on_rows_inserted(self, parent, start, end):
595727
if next_data is not None:
596728
assert c.next == next_data
597729

730+
def _on_rows_about_to_be_moved(
731+
self, source_parent, source_start, source_end, dest_parent, dest_row
732+
):
733+
assert self._change_in_flight is None
734+
self._change_in_flight = _ChangeInFlight.ROWS_MOVED
735+
self._debug(
736+
"rows about to be moved: source start {}, source end {}, "
737+
"source parent {}, destination parent {}, "
738+
"destination row {}".format(
739+
source_start,
740+
source_end,
741+
self._modelindex_debug(source_parent),
742+
self._modelindex_debug(dest_parent),
743+
dest_row,
744+
)
745+
)
746+
747+
def _on_rows_moved(
748+
self, source_parent, source_start, source_end, dest_parent, dest_row
749+
):
750+
assert self._change_in_flight == _ChangeInFlight.ROWS_MOVED
751+
self._change_in_flight = None
752+
self._debug(
753+
"rows moved: source start {}, source end {}, "
754+
"source parent {}, destination parent {}, "
755+
"destination row {}".format(
756+
source_start,
757+
source_end,
758+
self._modelindex_debug(source_parent),
759+
self._modelindex_debug(dest_parent),
760+
dest_row,
761+
)
762+
)
763+
598764
def _on_layout_about_to_be_changed(self):
765+
assert self._change_in_flight is None
766+
self._change_in_flight = _ChangeInFlight.LAYOUT_CHANGED
767+
599768
for i in range(max(self._model.rowCount(), 100)):
600769
idx = qt_api.QtCore.QPersistentModelIndex(self._model.index(i, 0))
601770
self._changing.append(idx)
602771

603772
def _on_layout_changed(self):
773+
assert self._change_in_flight == _ChangeInFlight.LAYOUT_CHANGED
774+
self._change_in_flight = None
775+
604776
for p in self._changing:
605777
assert p == self._model.index(p.row(), p.column(), p.parent())
606778
self._changing = []
607779

780+
def _on_model_about_to_be_reset(self):
781+
assert self._change_in_flight is None
782+
self._change_in_flight = _ChangeInFlight.MODEL_RESET
783+
784+
def _on_model_reset(self):
785+
assert self._change_in_flight == _ChangeInFlight.MODEL_RESET
786+
self._change_in_flight = None
787+
608788
def _on_rows_about_to_be_removed(self, parent, start, end):
609789
"""Store what is about to be removed to make sure it actually happens.
610790
611791
This gets stored to make sure it actually happens in rowsRemoved.
612792
"""
793+
assert self._change_in_flight is None
794+
self._change_in_flight = _ChangeInFlight.ROWS_REMOVED
795+
613796
parent_rowcount = self._model.rowCount(parent)
614797
last_index = (
615798
self._model.index(start - 1, 0, parent)
@@ -648,6 +831,9 @@ def _on_rows_about_to_be_removed(self, parent, start, end):
648831

649832
def _on_rows_removed(self, parent, start, end):
650833
"""Confirm that what was said was going to happen actually did."""
834+
assert self._change_in_flight == _ChangeInFlight.ROWS_REMOVED
835+
self._change_in_flight = None
836+
651837
c = self._remove.pop()
652838
last_data = (
653839
self._model.data(self._model.index(start - 1, 0, c.parent))

0 commit comments

Comments
 (0)