9 """ |
9 """ |
10 |
10 |
11 import contextlib |
11 import contextlib |
12 import copy |
12 import copy |
13 import locale |
13 import locale |
|
14 |
|
15 from collections import Counter |
14 from operator import attrgetter |
16 from operator import attrgetter |
15 |
17 |
16 from PyQt6.QtCore import ( |
18 from PyQt6.QtCore import ( |
17 pyqtSignal, pyqtSlot, Qt, QAbstractItemModel, QCoreApplication, QModelIndex |
19 pyqtSignal, pyqtSlot, Qt, QAbstractItemModel, QCoreApplication, QModelIndex |
18 ) |
20 ) |
29 |
31 |
30 |
32 |
31 class TestResultsModel(QAbstractItemModel): |
33 class TestResultsModel(QAbstractItemModel): |
32 """ |
34 """ |
33 Class implementing the item model containing the test data. |
35 Class implementing the item model containing the test data. |
|
36 |
|
37 @signal summary(str) emitted whenever the model data changes. The element |
|
38 is a summary of the test results of the model. |
34 """ |
39 """ |
|
40 summary = pyqtSignal(str) |
|
41 |
35 Headers = [ |
42 Headers = [ |
36 QCoreApplication.translate("TestResultsModel", "Status"), |
43 QCoreApplication.translate("TestResultsModel", "Status"), |
37 QCoreApplication.translate("TestResultsModel", "Name"), |
44 QCoreApplication.translate("TestResultsModel", "Name"), |
38 QCoreApplication.translate("TestResultsModel", "Message"), |
45 QCoreApplication.translate("TestResultsModel", "Message"), |
39 QCoreApplication.translate("TestResultsModel", "Duration (ms)"), |
46 QCoreApplication.translate("TestResultsModel", "Duration (ms)"), |
127 elif column == TestResultsModel.MessageColumn: |
134 elif column == TestResultsModel.MessageColumn: |
128 return self.__testResults[row].message |
135 return self.__testResults[row].message |
129 elif column == TestResultsModel.DurationColumn: |
136 elif column == TestResultsModel.DurationColumn: |
130 duration = self.__testResults[row].duration |
137 duration = self.__testResults[row].duration |
131 return ( |
138 return ( |
132 '' |
139 "" |
133 if duration is None else |
140 if duration is None else |
134 locale.format_string("%.2f", duration, grouping=True) |
141 locale.format_string("%.2f", duration, grouping=True) |
135 ) |
142 ) |
136 elif role == Qt.ItemDataRole.ToolTipRole: |
143 elif role == Qt.ItemDataRole.ToolTipRole: |
137 if idx == TopLevelId and column == TestResultsModel.NameColumn: |
144 if idx == TopLevelId and column == TestResultsModel.NameColumn: |
287 @type list of UTTestResult |
294 @type list of UTTestResult |
288 """ |
295 """ |
289 self.beginResetModel() |
296 self.beginResetModel() |
290 self.__testResults = copy.deepcopy(testResults) |
297 self.__testResults = copy.deepcopy(testResults) |
291 self.endResetModel() |
298 self.endResetModel() |
|
299 |
|
300 self.summary.emit(self.__summary()) |
292 |
301 |
293 def addTestResults(self, testResults): |
302 def addTestResults(self, testResults): |
294 """ |
303 """ |
295 Public method to add test results to the ones already managed by the |
304 Public method to add test results to the ones already managed by the |
296 model. |
305 model. |
301 firstRow = len(self.__testResults) |
310 firstRow = len(self.__testResults) |
302 lastRow = firstRow + len(testResults) - 1 |
311 lastRow = firstRow + len(testResults) - 1 |
303 self.beginInsertRows(QModelIndex(), firstRow, lastRow) |
312 self.beginInsertRows(QModelIndex(), firstRow, lastRow) |
304 self.__testResults.extend(testResults) |
313 self.__testResults.extend(testResults) |
305 self.endInsertRows() |
314 self.endInsertRows() |
|
315 |
|
316 self.summary.emit(self.__summary()) |
306 |
317 |
307 def updateTestResults(self, testResults): |
318 def updateTestResults(self, testResults): |
308 """ |
319 """ |
309 Public method to update the data of managed test result items. |
320 Public method to update the data of managed test result items. |
310 |
321 |
311 @param testResults test results to be updated |
322 @param testResults test results to be updated |
312 @type list of UTTestResult |
323 @type list of UTTestResult |
313 """ |
324 """ |
314 minIndex = None |
325 minIndex = None |
315 maxIndex = None |
326 maxIndex = None |
|
327 |
|
328 testResultsToBeAdded = [] |
316 |
329 |
317 for testResult in testResults: |
330 for testResult in testResults: |
318 for (index, currentResult) in enumerate(self.__testResults): |
331 for (index, currentResult) in enumerate(self.__testResults): |
319 if currentResult.id == testResult.id: |
332 if currentResult.id == testResult.id: |
320 self.__testResults[index] = testResult |
333 self.__testResults[index] = testResult |
322 minIndex = index |
335 minIndex = index |
323 maxIndex = index |
336 maxIndex = index |
324 else: |
337 else: |
325 minIndex = min(minIndex, index) |
338 minIndex = min(minIndex, index) |
326 maxIndex = max(maxIndex, index) |
339 maxIndex = max(maxIndex, index) |
|
340 |
|
341 break |
|
342 else: |
|
343 # Test result with given id was not found. |
|
344 # Just add it to the list (could be a sub test) |
|
345 testResultsToBeAdded.append(testResult) |
327 |
346 |
328 if minIndex is not None: |
347 if minIndex is not None: |
329 self.dataChanged.emit( |
348 self.dataChanged.emit( |
330 self.index(minIndex, 0), |
349 self.index(minIndex, 0), |
331 self.index(maxIndex, len(TestResultsModel.Headers) - 1) |
350 self.index(maxIndex, len(TestResultsModel.Headers) - 1) |
332 ) |
351 ) |
|
352 |
|
353 self.summary.emit(self.__summary()) |
|
354 |
|
355 if testResultsToBeAdded: |
|
356 self.addTestResults(testResultsToBeAdded) |
|
357 |
|
358 def __summary(self): |
|
359 """ |
|
360 Private method to generate a test results summary text. |
|
361 |
|
362 @return test results summary text |
|
363 @rtype str |
|
364 """ |
|
365 if len(self.__testResults) == 0: |
|
366 return self.tr("No results to show") |
|
367 |
|
368 counts = Counter(res.category for res in self.__testResults) |
|
369 if all( |
|
370 counts[category] == 0 |
|
371 for category in (ResultCategory.FAIL, ResultCategory.OK, |
|
372 ResultCategory.SKIP) |
|
373 ): |
|
374 return self.tr("Collected %n test(s)", "", len(self.__testResults)) |
|
375 |
|
376 return self.tr( |
|
377 "%n test(s)/subtest(s) total, {0} failed, {1} passed," |
|
378 " {2} skipped, {3} pending", |
|
379 "", len(self.__testResults) |
|
380 ).format( |
|
381 counts[ResultCategory.FAIL], |
|
382 counts[ResultCategory.OK], |
|
383 counts[ResultCategory.SKIP], |
|
384 counts[ResultCategory.PENDING] |
|
385 ) |
333 |
386 |
334 |
387 |
335 class TestResultsTreeView(QTreeView): |
388 class TestResultsTreeView(QTreeView): |
336 """ |
389 """ |
337 Class implementing a tree view to show the test result data. |
390 Class implementing a tree view to show the test result data. |