/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "autotesticons.h" #include "autotestplugin.h" #include "testresultdelegate.h" #include "testresultmodel.h" #include "testsettings.h" #include #include #include #include namespace Autotest { namespace Internal { /********************************* TestResultItem ******************************************/ TestResultItem::TestResultItem(const TestResultPtr &testResult) : m_testResult(testResult) { } static QIcon testResultIcon(ResultType result) { const static QIcon icons[] = { Icons::RESULT_PASS.icon(), Icons::RESULT_FAIL.icon(), Icons::RESULT_XFAIL.icon(), Icons::RESULT_XPASS.icon(), Icons::RESULT_SKIP.icon(), Icons::RESULT_BLACKLISTEDPASS.icon(), Icons::RESULT_BLACKLISTEDFAIL.icon(), Icons::RESULT_BLACKLISTEDXPASS.icon(), Icons::RESULT_BLACKLISTEDXFAIL.icon(), Icons::RESULT_BENCHMARK.icon(), Icons::RESULT_MESSAGEDEBUG.icon(), Icons::RESULT_MESSAGEDEBUG.icon(), // Info gets the same handling as Debug for now Icons::RESULT_MESSAGEWARN.icon(), Icons::RESULT_MESSAGEFATAL.icon(), Icons::RESULT_MESSAGEFATAL.icon(), // System gets same handling as Fatal for now QIcon(), Icons::RESULT_MESSAGEPASSWARN.icon(), Icons::RESULT_MESSAGEFAILWARN.icon(), ProjectExplorer::Icons::DESKTOP_DEVICE.icon(), // for now }; // provide an icon for unknown?? if (result < ResultType::FIRST_TYPE || result >= ResultType::MessageInternal) { switch (result) { case ResultType::MessageTestCaseSuccess: return icons[int(ResultType::Pass)]; case ResultType::MessageTestCaseFail: return icons[int(ResultType::Fail)]; case ResultType::MessageTestCaseSuccessWarn: return icons[16]; case ResultType::MessageTestCaseFailWarn: return icons[17]; case ResultType::Application: return icons[18]; default: return QIcon(); } } return icons[int(result)]; } QVariant TestResultItem::data(int column, int role) const { switch (role) { case Qt::DecorationRole: { if (!m_testResult) return QVariant(); const ResultType result = m_testResult->result(); if (result == ResultType::MessageLocation && parent()) return parent()->data(column, role); return testResultIcon(result); } case Qt::DisplayRole: return m_testResult ? m_testResult->outputString(true) : QVariant(); default: return Utils::TreeItem::data(column, role); } } void TestResultItem::updateDescription(const QString &description) { QTC_ASSERT(m_testResult, return); m_testResult->setDescription(description); } void TestResultItem::updateResult(bool &changed, ResultType addedChildType) { changed = false; const ResultType old = m_testResult->result(); if (old == ResultType::MessageTestCaseFailWarn) // can't become worse return; if (!TestResult::isMessageCaseStart(old)) return; ResultType newResult = old; switch (addedChildType) { case ResultType::Fail: case ResultType::MessageFatal: case ResultType::UnexpectedPass: case ResultType::MessageTestCaseFail: newResult = (old == ResultType::MessageTestCaseSuccessWarn) ? ResultType::MessageTestCaseFailWarn : ResultType::MessageTestCaseFail; break; case ResultType::MessageTestCaseFailWarn: newResult = ResultType::MessageTestCaseFailWarn; break; case ResultType::ExpectedFail: case ResultType::MessageWarn: case ResultType::MessageSystem: case ResultType::Skip: case ResultType::BlacklistedFail: case ResultType::BlacklistedPass: case ResultType::BlacklistedXFail: case ResultType::BlacklistedXPass: case ResultType::MessageTestCaseSuccessWarn: newResult = (old == ResultType::MessageTestCaseFail) ? ResultType::MessageTestCaseFailWarn : ResultType::MessageTestCaseSuccessWarn; break; case ResultType::Pass: case ResultType::MessageTestCaseSuccess: newResult = (old == ResultType::TestStart) ? ResultType::MessageTestCaseSuccess : old; break; default: break; } changed = old != newResult; if (changed) m_testResult->setResult(newResult); } TestResultItem *TestResultItem::intermediateFor(const TestResultItem *item) const { QTC_ASSERT(item, return nullptr); const TestResult *otherResult = item->testResult(); for (int row = childCount() - 1; row >= 0; --row) { TestResultItem *child = childAt(row); const TestResult *testResult = child->testResult(); if (testResult->result() != ResultType::TestStart) continue; if (testResult->isIntermediateFor(otherResult)) return child; } return nullptr; } TestResultItem *TestResultItem::createAndAddIntermediateFor(const TestResultItem *child) { TestResultPtr result(m_testResult->createIntermediateResultFor(child->testResult())); QTC_ASSERT(!result.isNull(), return nullptr); result->setResult(ResultType::TestStart); TestResultItem *intermediate = new TestResultItem(result); appendChild(intermediate); return intermediate; } /********************************* TestResultModel *****************************************/ TestResultModel::TestResultModel(QObject *parent) : Utils::TreeModel(new TestResultItem(TestResultPtr()), parent) { } void TestResultModel::updateParent(const TestResultItem *item) { QTC_ASSERT(item, return); QTC_ASSERT(item->testResult(), return); TestResultItem *parentItem = item->parent(); if (parentItem == rootItem()) // do not update invisible root item return; bool changed = false; parentItem->updateResult(changed, item->testResult()->result()); if (!changed) return; emit dataChanged(parentItem->index(), parentItem->index()); updateParent(parentItem); } void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand) { const int lastRow = rootItem()->childCount() - 1; if (testResult->result() == ResultType::MessageCurrentTest) { // MessageCurrentTest should always be the last top level item if (lastRow >= 0) { TestResultItem *current = rootItem()->childAt(lastRow); const TestResult *result = current->testResult(); if (result && result->result() == ResultType::MessageCurrentTest) { current->updateDescription(testResult->description()); emit dataChanged(current->index(), current->index()); return; } } rootItem()->appendChild(new TestResultItem(testResult)); return; } m_testResultCount[testResult->result()]++; TestResultItem *newItem = new TestResultItem(testResult); TestResultItem *root = nullptr; if (AutotestPlugin::settings()->displayApplication) { const QString application = testResult->id(); if (!application.isEmpty()) { root = rootItem()->findFirstLevelChild([&application](TestResultItem *child) { QTC_ASSERT(child, return false); return child->testResult()->id() == application; }); if (!root) { TestResult *tmpAppResult = new TestResult(application, application); tmpAppResult->setResult(ResultType::Application); root = new TestResultItem(TestResultPtr(tmpAppResult)); if (lastRow >= 0) rootItem()->insertChild(lastRow, root); else rootItem()->appendChild(root); } } } TestResultItem *parentItem = findParentItemFor(newItem, root); addFileName(testResult->fileName()); // ensure we calculate the results pane correctly if (parentItem) { parentItem->appendChild(newItem); if (autoExpand) parentItem->expand(); updateParent(newItem); } else { if (lastRow >= 0) { TestResultItem *current = rootItem()->childAt(lastRow); const TestResult *result = current->testResult(); if (result && result->result() == ResultType::MessageCurrentTest) { rootItem()->insertChild(current->index().row(), newItem); return; } } // there is no MessageCurrentTest at the last row, but we have a toplevel item - just add it rootItem()->appendChild(newItem); } } void TestResultModel::removeCurrentTestMessage() { TestResultItem *currentMessageItem = rootItem()->findFirstLevelChild([](TestResultItem *it) { return (it->testResult()->result() == ResultType::MessageCurrentTest); }); if (currentMessageItem) destroyItem(currentMessageItem); } void TestResultModel::clearTestResults() { clear(); m_testResultCount.clear(); m_disabled = 0; m_fileNames.clear(); m_maxWidthOfFileName = 0; m_widthOfLineNumber = 0; } const TestResult *TestResultModel::testResult(const QModelIndex &idx) { if (idx.isValid()) return itemForIndex(idx)->testResult(); return nullptr; } void TestResultModel::recalculateMaxWidthOfFileName(const QFont &font) { const QFontMetrics fm(font); m_maxWidthOfFileName = 0; for (const QString &fileName : m_fileNames) { int pos = fileName.lastIndexOf('/'); m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.horizontalAdvance(fileName.mid(pos + 1))); } } void TestResultModel::addFileName(const QString &fileName) { const QFontMetrics fm(m_measurementFont); int pos = fileName.lastIndexOf('/'); m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.horizontalAdvance(fileName.mid(pos + 1))); m_fileNames.insert(fileName); } int TestResultModel::maxWidthOfFileName(const QFont &font) { if (font != m_measurementFont) recalculateMaxWidthOfFileName(font); return m_maxWidthOfFileName; } int TestResultModel::maxWidthOfLineNumber(const QFont &font) { if (m_widthOfLineNumber == 0 || font != m_measurementFont) { QFontMetrics fm(font); m_measurementFont = font; m_widthOfLineNumber = fm.horizontalAdvance("88888"); } return m_widthOfLineNumber; } TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item, const TestResultItem *startItem) const { QTC_ASSERT(item, return nullptr); TestResultItem *root = startItem ? const_cast(startItem) : nullptr; const TestResult *result = item->testResult(); const QString &name = result->name(); const QString &id = result->id(); if (root == nullptr && !name.isEmpty()) { for (int row = rootItem()->childCount() - 1; row >= 0; --row) { TestResultItem *tmp = rootItem()->childAt(row); auto tmpTestResult = tmp->testResult(); if (tmpTestResult->id() == id && tmpTestResult->name() == name) { root = tmp; break; } } } if (root == nullptr) return root; bool needsIntermediate = false; auto predicate = [result, &needsIntermediate](Utils::TreeItem *it) { TestResultItem *currentItem = static_cast(it); return currentItem->testResult()->isDirectParentOf(result, &needsIntermediate); }; TestResultItem *parent = root->reverseFindAnyChild(predicate); if (parent) { if (needsIntermediate) { // check if the intermediate is present already if (TestResultItem *intermediate = parent->intermediateFor(item)) return intermediate; return parent->createAndAddIntermediateFor(item); } return parent; } return root; } /********************************** Filter Model **********************************/ TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent) : QSortFilterProxyModel(parent), m_sourceModel(sourceModel) { setSourceModel(sourceModel); enableAllResultTypes(); } void TestResultFilterModel::enableAllResultTypes() { m_enabled << ResultType::Pass << ResultType::Fail << ResultType::ExpectedFail << ResultType::UnexpectedPass << ResultType::Skip << ResultType::MessageDebug << ResultType::MessageWarn << ResultType::MessageInternal << ResultType::MessageLocation << ResultType::MessageFatal << ResultType::Invalid << ResultType::BlacklistedPass << ResultType::BlacklistedFail << ResultType::BlacklistedXFail << ResultType::BlacklistedXPass << ResultType::Benchmark << ResultType::MessageCurrentTest << ResultType::TestStart << ResultType::MessageTestCaseSuccess << ResultType::MessageTestCaseSuccessWarn << ResultType::MessageTestCaseFail << ResultType::MessageTestCaseFailWarn << ResultType::TestEnd << ResultType::MessageInfo << ResultType::MessageSystem << ResultType::Application; invalidateFilter(); } void TestResultFilterModel::toggleTestResultType(ResultType type) { if (m_enabled.contains(type)) { m_enabled.remove(type); if (type == ResultType::MessageInternal) m_enabled.remove(ResultType::TestEnd); if (type == ResultType::MessageDebug) m_enabled.remove(ResultType::MessageInfo); if (type == ResultType::MessageWarn) m_enabled.remove(ResultType::MessageSystem); } else { m_enabled.insert(type); if (type == ResultType::MessageInternal) m_enabled.insert(ResultType::TestEnd); if (type == ResultType::MessageDebug) m_enabled.insert(ResultType::MessageInfo); if (type == ResultType::MessageWarn) m_enabled.insert(ResultType::MessageSystem); } invalidateFilter(); } void TestResultFilterModel::clearTestResults() { m_sourceModel->clearTestResults(); } bool TestResultFilterModel::hasResults() { return rowCount(QModelIndex()); } const TestResult *TestResultFilterModel::testResult(const QModelIndex &index) const { return m_sourceModel->testResult(mapToSource(index)); } bool TestResultFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = m_sourceModel->index(sourceRow, 0, sourceParent); if (!index.isValid()) return false; ResultType resultType = m_sourceModel->testResult(index)->result(); switch (resultType) { case ResultType::MessageTestCaseSuccess: return m_enabled.contains(ResultType::Pass); case ResultType::MessageTestCaseFail: case ResultType::MessageTestCaseSuccessWarn: case ResultType::MessageTestCaseFailWarn: return acceptTestCaseResult(index); default: return m_enabled.contains(resultType); } } bool TestResultFilterModel::acceptTestCaseResult(const QModelIndex &srcIndex) const { for (int row = 0, count = m_sourceModel->rowCount(srcIndex); row < count; ++row) { const QModelIndex &child = m_sourceModel->index(row, 0, srcIndex); ResultType type = m_sourceModel->testResult(child)->result(); if (type == ResultType::MessageTestCaseSuccess) type = ResultType::Pass; if (type == ResultType::MessageTestCaseFail || type == ResultType::MessageTestCaseFailWarn || type == ResultType::MessageTestCaseSuccessWarn) { if (acceptTestCaseResult(child)) return true; } else if (m_enabled.contains(type)) { return true; } } return false; } } // namespace Internal } // namespace Autotest