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)