File indexing completed on 2024-04-21 04:01:51

0001 """
0002 ##
0003 ## Copyright (C) 2007 Trolltech ASA. All rights reserved.
0004 ## Copyright (C) 2010-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0005 ##
0006 ## This file is part of the Qt Concurrent project on Trolltech Labs.
0007 ##
0008 ## This file may be used under the terms of the GNU General Public
0009 ## License version 2.0 as published by the Free Software Foundation
0010 ## and appearing in the file LICENSE.GPL included in the packaging of
0011 ## this file.  Please review the following information to ensure GNU
0012 ## General Public Licensing requirements will be met:
0013 ## https://www.qt.io/download-open-source
0014 ##
0015 ## If you are unsure which license is appropriate for your use, please
0016 ## review the following information:
0017 ## https://www.qt.io/licensing/ or contact the
0018 ## sales department at https://www.qt.io/contact-us/sales-contact-request/?hsLang=en
0019 ##
0020 ## This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
0021 ## WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
0022 ##
0023 #############################################################################
0024 
0025 There are several variations of python ports for this script floating
0026 around, it seemed easier to me to maintain one for myself
0027 """
0028 
0029 try:
0030     from PyQt5 import sip
0031 except ImportError:
0032     import sip
0033 from qt import QObject, Qt, QAbstractItemModel, QModelIndex
0034 from qt import QPersistentModelIndex, QFont, QColor, QSize
0035 from common import isAlive
0036 
0037 # pylint: skip-file
0038 
0039 def isValid(variant):
0040     return (variant is not None)
0041 
0042 
0043 class ModelTest(QObject):
0044 
0045     """tests a model"""
0046 
0047     def __init__(self, _model, parent):
0048         """
0049         Connect to all of the models signals, Whenever anything happens recheck everything.
0050         """
0051         QObject.__init__(self, parent)
0052         self._model = _model
0053         self.model = sip.cast(_model, QAbstractItemModel)
0054         self.insert = []
0055         self.remove = []
0056         self.changing = []
0057         self.fetchingMore = False
0058         assert(self.model)
0059 
0060         self.model.columnsAboutToBeInserted.connect(self.runAllTests)
0061         self.model.columnsAboutToBeRemoved.connect(self.runAllTests)
0062         self.model.columnsInserted.connect(self.runAllTests)
0063         self.model.columnsRemoved.connect(self.runAllTests)
0064         self.model.dataChanged.connect(self.runAllTests)
0065         self.model.headerDataChanged.connect(self.runAllTests)
0066         self.model.layoutAboutToBeChanged.connect(self.runAllTests)
0067         self.model.layoutChanged.connect(self.runAllTests)
0068         self.model.modelReset.connect(self.runAllTests)
0069         self.model.rowsAboutToBeInserted.connect(self.runAllTests)
0070         self.model.rowsAboutToBeRemoved.connect(self.runAllTests)
0071         self.model.rowsInserted.connect(self.runAllTests)
0072         self.model.rowsRemoved.connect(self.runAllTests)
0073 
0074         # Special checks for inserting/removing
0075         self.model.layoutAboutToBeChanged.connect(self.layoutAboutToBeChanged)
0076         self.model.layoutChanged.connect(self.layoutChanged)
0077         self.model.rowsAboutToBeInserted.connect(self.rowsAboutToBeInserted)
0078         self.model.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved)
0079         self.model.rowsInserted.connect(self.rowsInserted)
0080         self.model.rowsRemoved.connect(self.rowsRemoved)
0081         self.runAllTests()
0082 
0083     def nonDestructiveBasicTest(self):
0084         """
0085         nonDestructiveBasicTest tries to call a number of the basic functions (not all)
0086         to make sure the model doesn't outright segfault, testing the functions that makes sense.
0087         """
0088         assert(self.model.buddy(QModelIndex()) == QModelIndex())
0089         self.model.canFetchMore(QModelIndex())
0090         assert(self.model.columnCount(QModelIndex()) >= 0)
0091         assert(self.model.data(QModelIndex(), Qt.DisplayRole) is None)
0092         self.fetchingMore = True
0093         self.model.fetchMore(QModelIndex())
0094         self.fetchingMore = False
0095         flags = self.model.flags(QModelIndex())
0096         assert(int(flags & Qt.ItemIsEnabled) == Qt.ItemIsEnabled or
0097                int(flags & Qt.ItemIsEnabled) == 0)
0098         self.model.hasChildren(QModelIndex())
0099         self.model.hasIndex(0, 0)
0100         self.model.headerData(0, Qt.Horizontal, Qt.DisplayRole)
0101         self.model.index(0, 0, QModelIndex())
0102         self.model.itemData(QModelIndex())
0103         cache = None
0104         self.model.match(QModelIndex(), -1, cache)
0105         self.model.mimeTypes()
0106         assert(self.model.parent(QModelIndex()) == QModelIndex())
0107         assert(self.model.rowCount(QModelIndex()) >= 0)
0108         variant = None
0109         self.model.setData(QModelIndex(), variant, -1)
0110         self.model.setHeaderData(-1, Qt.Horizontal, None)
0111         self.model.setHeaderData(0, Qt.Horizontal, None)
0112         self.model.setHeaderData(999999, Qt.Horizontal, None)
0113         self.model.sibling(0, 0, QModelIndex())
0114         self.model.span(QModelIndex())
0115         self.model.supportedDropActions()
0116 
0117     def rowCount(self):
0118         """
0119         Tests self.model's implementation of QAbstractItemModel::rowCount() and hasChildren()
0120 
0121         self.models that are dynamically populated are not as fully tested here.
0122         """
0123         # check top row
0124         topindex = self.model.index(0, 0, QModelIndex())
0125         rows = self.model.rowCount(topindex)
0126         assert(rows >= 0)
0127         if rows > 0:
0128             assert(self.model.hasChildren(topindex))
0129 
0130         secondlvl = self.model.index(0, 0, topindex)
0131         if secondlvl.isValid():
0132             # check a row count where parent is valid
0133             rows = self.model.rowCount(secondlvl)
0134             assert(rows >= 0)
0135             if rows > 0:
0136                 assert(self.model.hasChildren(secondlvl))
0137 
0138         # The self.models rowCount() is tested more extensively in checkChildren,
0139         # but this catches the big mistakes
0140 
0141     def columnCount(self):
0142         """
0143         Tests self.model's implementation of QAbstractItemModel::columnCount() and hasChildren()
0144         """
0145         # check top row
0146         topidx = self.model.index(0, 0, QModelIndex())
0147         assert(self.model.columnCount(topidx) >= 0)
0148 
0149         # check a column count where parent is valid
0150         childidx = self.model.index(0, 0, topidx)
0151         if childidx.isValid():
0152             assert(self.model.columnCount(childidx) >= 0)
0153 
0154         # columnCount() is tested more extensively in checkChildren,
0155         # but this catches the big mistakes
0156 
0157     def hasIndex(self):
0158         """
0159         Tests self.model's implementation of QAbstractItemModel::hasIndex()
0160         """
0161         # Make sure that invalid values returns an invalid index
0162         assert(self.model.hasIndex(-2, -2) == False)
0163         assert(self.model.hasIndex(-2, 0) == False)
0164         assert(self.model.hasIndex(0, -2) == False)
0165 
0166         rows = self.model.rowCount(QModelIndex())
0167         cols = self.model.columnCount(QModelIndex())
0168 
0169         # check out of bounds
0170         assert(self.model.hasIndex(rows, cols) == False)
0171         assert(self.model.hasIndex(rows + 1, cols + 1) == False)
0172 
0173         if rows > 0:
0174             assert(self.model.hasIndex(0, 0))
0175 
0176         # hasIndex() is tested more extensively in checkChildren()
0177         # but this catches the big mistakes
0178 
0179     def index(self):
0180         """
0181         Tests self.model's implementation of QAbstractItemModel::index()
0182         """
0183         # Make sure that invalid values returns an invalid index
0184         assert(self.model.index(-2, -2, QModelIndex()) == QModelIndex())
0185         assert(self.model.index(-2, 0, QModelIndex()) == QModelIndex())
0186         assert(self.model.index(0, -2, QModelIndex()) == QModelIndex())
0187 
0188         rows = self.model.rowCount(QModelIndex())
0189         cols = self.model.columnCount(QModelIndex())
0190 
0191         if rows == 0:
0192             return
0193 
0194         # Catch off by one errors
0195         assert(self.model.index(rows, cols, QModelIndex()) == QModelIndex())
0196         assert(self.model.index(0, 0, QModelIndex()).isValid())
0197 
0198         # Make sure that the same index is *always* returned
0199         idx1 = self.model.index(0, 0, QModelIndex())
0200         idx2 = self.model.index(0, 0, QModelIndex())
0201         assert(idx1 == idx2)
0202 
0203         # index() is tested more extensively in checkChildren()
0204         # but this catches the big mistakes
0205 
0206     def parent(self):
0207         """
0208         Tests self.model's implementation of QAbstractItemModel::parent()
0209         """
0210         # Make sure the self.model wont crash and will return an invalid QModelIndex
0211         # when asked for the parent of an invalid index
0212         assert(self.model.parent(QModelIndex()) == QModelIndex())
0213 
0214         if self.model.rowCount(QModelIndex()) == 0:
0215             return
0216 
0217         # Column 0              | Column 1  |
0218         # QtCore.Qself.modelIndex()         |           |
0219         #    \- topidx          | topidx1   |
0220         #         \- childix    | childidx1 |
0221 
0222         # Common error test #1, make sure that a top level index has a parent
0223         # that is an invalid QtCore.Qself.modelIndex
0224         topidx = self.model.index(0, 0, QModelIndex())
0225         assert(self.model.parent(topidx) == QModelIndex())
0226 
0227         # Common error test #2, make sure that a second level index has a parent
0228         # that is the first level index
0229         if self.model.rowCount(topidx) > 0:
0230             childidx = self.model.index(0, 0, topidx)
0231             assert(self.model.parent(childidx) == topidx)
0232 
0233         # Common error test #3, the second column should NOT have the same children
0234         # as the first column in a row
0235         # Usually the second column shouldn't have children
0236         topidx1 = self.model.index(0, 1, QModelIndex())
0237         if self.model.rowCount(topidx1) > 0:
0238             childidx = self.model.index(0, 0, topidx)
0239             childidx1 = self.model.index(0, 0, topidx1)
0240             assert(childidx != childidx1)
0241 
0242         # Full test, walk n levels deep through the self.model making sure that all
0243         # parent's children correctly specify their parent
0244         self.checkChildren(QModelIndex())
0245 
0246     def testRoleDataType(self, role, expectedType):
0247         """Tests implementation if model.data() for role"""
0248         model = self.model
0249         result = model.data(model.index(0, 0, QModelIndex()), role)
0250         if result is not None and not isinstance(result, expectedType):
0251             raise Exception('returned data type is {}, should be {}'.format(
0252                 type(result), expectedType))
0253 
0254     def testRoleDataValues(self, role, asserter):
0255         """Tests implementation if model.data() for role.
0256         asserter is a function returning True or False"""
0257         model = self.model
0258         result = model.data(model.index(0, 0, QModelIndex()), role)
0259         if result is not None and not asserter(result):
0260             raise Exception('returned value {} is wrong')
0261 
0262     def data(self):
0263         """
0264         Tests self.model's implementation of QAbstractItemModel::data()
0265         """
0266         # Invalid index should return an invalid qvariant
0267         assert not isValid(self.model.data(QModelIndex(), Qt.DisplayRole))
0268 
0269         if self.model.rowCount(QModelIndex()) == 0:
0270             return
0271 
0272         # A valid index should have a valid data
0273         assert isValid(self.model.index(0, 0, QModelIndex()))
0274 
0275         # shouldn't be able to set data on an invalid index
0276         assert(
0277             self.model.setData(QModelIndex(),
0278                                "foo",
0279                                Qt.DisplayRole) == False)
0280 
0281         # General Purpose roles that should return a QString
0282         self.testRoleDataType(Qt.ToolTipRole, str)
0283         self.testRoleDataType(Qt.StatusTipRole, str)
0284         self.testRoleDataType(Qt.WhatsThisRole, str)
0285         self.testRoleDataType(Qt.SizeHintRole, QSize)
0286         self.testRoleDataType(Qt.FontRole, QFont)
0287         self.testRoleDataType(Qt.ForegroundRole, QColor)
0288         self.testRoleDataType(Qt.BackgroundColorRole, QColor)
0289         self.testRoleDataType(Qt.TextColorRole, QColor)
0290 
0291         # Check that the alignment is one we know about
0292         self.testRoleDataValues(
0293             Qt.TextAlignmentRole,
0294             lambda x: x == (x & int(Qt.AlignHorizontal_Mask | Qt.AlignVertical_Mask)))
0295 
0296         # General Purpose roles that should return a QColor
0297         # Check that the "check state" is one we know about.
0298         self.testRoleDataValues(
0299             Qt.CheckStateRole,
0300             lambda x: x in (Qt.Unchecked, Qt.PartiallyChecked, Qt.Checked))
0301 
0302     def runAllTests(self):
0303         """run all tests after the model changed"""
0304         if not isAlive(self):
0305             return
0306         if self.fetchingMore:
0307             return
0308         self.nonDestructiveBasicTest()
0309         self.rowCount()
0310         self.columnCount()
0311         self.hasIndex()
0312         self.index()
0313         self.parent()
0314         self.data()
0315 
0316     def rowsAboutToBeInserted(self, parent, start, unusedEnd):
0317         """
0318         Store what is about to be inserted to make sure it actually happens
0319         """
0320         item = {}
0321         item['parent'] = parent
0322         item['oldSize'] = self.model.rowCount(parent)
0323         item['last'] = self.model.data(self.model.index(start - 1, 0, parent))
0324         item['next'] = self.model.data(self.model.index(start, 0, parent))
0325         self.insert.append(item)
0326 
0327     def rowsInserted(self, parent, start, end):
0328         """
0329         Confirm that what was said was going to happen actually did
0330         """
0331         item = self.insert.pop()
0332         assert(item['parent'] == parent)
0333         assert(item['oldSize'] + (end - start + 1)
0334                == self.model.rowCount(parent))
0335         assert(item['last'] == self.model.data(
0336             self.model.index(start - 1, 0, item['parent'])))
0337 
0338         # if item['next'] != self.model.data(self.model.index(end+1, 0, item['parent'])):
0339         #   qDebug << start << end
0340         #   for i in range(0, self.model.rowCount(QModelIndex())):
0341         #       qDebug << self.model.index(i, 0).data().toString()
0342         # qDebug() << item['next'] << self.model.data(model.index(end+1, 0,
0343         # item['parent']))
0344 
0345         assert(item['next'] == self.model.data(
0346             self.model.index(end + 1, 0, item['parent'])))
0347 
0348     def rowsAboutToBeRemoved(self, parent, start, end):
0349         """
0350         Store what is about to be inserted to make sure it actually happens
0351         """
0352         item = {}
0353         item['parent'] = parent
0354         item['oldSize'] = self.model.rowCount(parent)
0355         item['last'] = self.model.data(self.model.index(start - 1, 0, parent))
0356         item['next'] = self.model.data(self.model.index(end + 1, 0, parent))
0357         self.remove.append(item)
0358 
0359     def rowsRemoved(self, parent, start, end):
0360         """
0361         Confirm that what was said was going to happen actually did
0362         """
0363         item = self.remove.pop()
0364         assert(item['parent'] == parent)
0365         assert(item['oldSize'] - (end - start + 1)
0366                == self.model.rowCount(parent))
0367         assert(item['last'] == self.model.data(
0368             self.model.index(start - 1, 0, item['parent'])))
0369         assert(item['next'] == self.model.data(
0370             self.model.index(start, 0, item['parent'])))
0371 
0372     def layoutAboutToBeChanged(self):
0373         """
0374         Store what is about to be changed
0375         """
0376         for i in range(0, max(0, min(self.model.rowCount(), 100))):
0377             self.changing.append(QPersistentModelIndex(self.model.index(i, 0)))
0378 
0379     def layoutChanged(self):
0380         """
0381         Confirm that what was said was going to happen actually did
0382         """
0383         for change in self.changing:
0384             assert(
0385                 change == self.model.index(change.row(),
0386                                            change.column(),
0387                                            change.parent()))
0388         self.changing = []
0389 
0390     def checkChildren(self, parent, depth=0):
0391         """
0392         Called from parent() test.
0393 
0394         A self.model that returns an index of parent X should also return X when asking
0395         for the parent of the index
0396 
0397         This recursive function does pretty extensive testing on the whole self.model in an
0398         effort to catch edge cases.
0399 
0400         This function assumes that rowCount(QModelIndex()), columnCount(QModelIndex()) and index() already work.
0401         If they have a bug it will point it out, but the above tests should have already
0402         found the basic bugs because it is easier to figure out the problem in
0403         those tests then this one
0404         """
0405         # First just try walking back up the tree.
0406         parentIdx = parent
0407         while parentIdx.isValid():
0408             parentIdx = parentIdx.parent()
0409 
0410         # For self.models that are dynamically populated
0411         if self.model.canFetchMore(parent):
0412             self.fetchingMore = True
0413             self.model.fetchMore(parent)
0414             self.fetchingMore = False
0415 
0416         rows = self.model.rowCount(parent)
0417         cols = self.model.columnCount(parent)
0418 
0419         if rows > 0:
0420             assert(self.model.hasChildren(parent))
0421 
0422         # Some further testing against rows(), columns, and hasChildren()
0423         assert(rows >= 0)
0424         assert(cols >= 0)
0425 
0426         if rows > 0:
0427             assert(self.model.hasChildren(parent))
0428 
0429         # qDebug() << "parent:" << self.model.data(parent).toString() << "rows:" << rows
0430         #          << "columns:" << cols << "parent column:" << parent.column()
0431 
0432         assert(self.model.hasIndex(rows + 1, 0, parent) == False)
0433         for row in range(0, rows):
0434             if self.model.canFetchMore(parent):
0435                 self.fetchingMore = True
0436                 self.model.fetchMore(parent)
0437                 self.fetchingMore = False
0438             assert(self.model.hasIndex(row, cols + 1, parent) == False)
0439             for column in range(0, cols):
0440                 assert(self.model.hasIndex(row, column, parent))
0441                 index = self.model.index(row, column, parent)
0442                 # rowCount(QModelIndex()) and columnCount(QModelIndex()) said
0443                 # that it existed...
0444                 assert(index.isValid())
0445 
0446                 # index() should always return the same index when called twice
0447                 # in a row
0448                 modIdx = self.model.index(row, column, parent)
0449                 assert(index == modIdx)
0450 
0451                 # Make sure we get the same index if we request it twice in a
0452                 # row
0453                 idx1 = self.model.index(row, column, parent)
0454                 idx2 = self.model.index(row, column, parent)
0455                 assert(idx1 == idx2)
0456 
0457                 # Some basic checking on the index that is returned
0458                 # assert( index.model() == self.model )
0459                 # This raises an error that is not part of the qbzr code.
0460                 # see
0461                 # https://www.riverbankcomputing.com/pipermail/pyqt/2011-February/029300.html
0462                 assert(index.row() == row)
0463                 assert(index.column() == column)
0464                 # While you can technically return a QVariant usually this is a sign
0465                 # if an bug in data() Disable if this really is ok in your
0466                 # self.model
0467                 assert isValid(self.model.data(index, Qt.DisplayRole))
0468 
0469                 # if the next test fails here is some somewhat useful debug you play with
0470                 # if self.model.parent(index) != parent:
0471                 #   qDebug() << row << column << depth << self.model.data(index).toString()
0472                 #        << self.model.data(parent).toString()
0473                 #   qDebug() << index << parent << self.model.parent(index)
0474                 # And a view that you can even use to show the self.model
0475                 # view = QtGui.QTreeView()
0476                 # view.setself.model(model)
0477                 # view.show()
0478                 #
0479 
0480                 # Check that we can get back our real parent
0481                 parentIdx = self.model.parent(index)
0482                 assert(parentIdx.internalId() == parent.internalId())
0483                 assert(parentIdx.row() == parent.row())
0484 
0485                 # recursively go down the children
0486                 if self.model.hasChildren(index) and depth < 10:
0487                     # qDebug() << row << column << "hasChildren" <<
0488                     # self.model.rowCount(index)
0489                     self.checkChildren(index, depth + 1)
0490                 # else:
0491                 #   if depth >= 10:
0492                 #       qDebug() << "checked 10 deep"
0493 
0494                 # Make sure that after testing the children that the index
0495                 # doesn't change
0496                 newIdx = self.model.index(row, column, parent)
0497                 assert(index == newIdx)