/**************************************************************************** ** ** 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 "testresultdelegate.h" #include "testresultmodel.h" #include #include #include namespace Autotest { namespace Internal { /********************************* TestResultItem ******************************************/ TestResultItem::TestResultItem(const TestResultPtr &testResult) : m_testResult(testResult) { } static QIcon testResultIcon(Result::Type 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_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(), }; // provide an icon for unknown?? if (result < 0 || result >= Result::MessageInternal) { switch (result) { case Result::MessageTestCaseSuccess: return icons[Result::Pass]; case Result::MessageTestCaseFail: return icons[Result::Fail]; case Result::MessageTestCaseSuccessWarn: return icons[14]; case Result::MessageTestCaseFailWarn: return icons[15]; default: return QIcon(); } } return icons[result]; } QVariant TestResultItem::data(int column, int role) const { switch (role) { case Qt::DecorationRole: { if (!m_testResult) return QVariant(); const Result::Type result = m_testResult->result(); if (result == Result::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, Result::Type addedChildType) { changed = false; const Result::Type old = m_testResult->result(); if (old == Result::MessageTestCaseFailWarn) // can't become worse return; if (!TestResult::isMessageCaseStart(old)) return; Result::Type newResult = Result::MessageTestCaseSuccess; switch (addedChildType) { case Result::Fail: case Result::MessageFatal: case Result::UnexpectedPass: case Result::MessageTestCaseFail: newResult = (old == Result::MessageTestCaseSuccessWarn) ? Result::MessageTestCaseFailWarn : Result::MessageTestCaseFail; break; case Result::MessageTestCaseFailWarn: newResult = Result::MessageTestCaseFailWarn; break; case Result::ExpectedFail: case Result::MessageWarn: case Result::MessageSystem: case Result::Skip: case Result::BlacklistedFail: case Result::BlacklistedPass: case Result::MessageTestCaseSuccessWarn: newResult = (old == Result::MessageTestCaseFail) ? Result::MessageTestCaseFailWarn : Result::MessageTestCaseSuccessWarn; break; case Result::Pass: case Result::MessageTestCaseSuccess: newResult = (old == Result::MessageIntermediate || old == Result::MessageTestCaseStart) ? Result::MessageTestCaseSuccess : old; break; default: return; } 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 = static_cast(childAt(row)); const TestResult *testResult = child->testResult(); if (testResult->result() != Result::MessageIntermediate) 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(Result::MessageIntermediate); TestResultItem *intermediate = new TestResultItem(result); appendChild(intermediate); return intermediate; } /********************************* TestResultModel *****************************************/ TestResultModel::TestResultModel(QObject *parent) : Utils::TreeModel<>(parent) { } void TestResultModel::updateParent(const TestResultItem *item) { QTC_ASSERT(item, return); QTC_ASSERT(item->testResult(), return); Utils::TreeItem *parentItem = item->parent(); if (parentItem == rootItem()) // do not update invisible root item return; bool changed = false; TestResultItem *parentResultItem = static_cast(parentItem); parentResultItem->updateResult(changed, item->testResult()->result()); if (!changed) return; emit dataChanged(parentItem->index(), parentItem->index()); updateParent(parentResultItem); } void TestResultModel::addTestResult(const TestResultPtr &testResult, bool autoExpand) { const int lastRow = rootItem()->childCount() - 1; if (testResult->result() == Result::MessageCurrentTest) { // MessageCurrentTest should always be the last top level item if (lastRow >= 0) { TestResultItem *current = static_cast(rootItem()->childAt(lastRow)); const TestResult *result = current->testResult(); if (result && result->result() == Result::MessageCurrentTest) { current->updateDescription(testResult->description()); emit dataChanged(current->index(), current->index()); return; } } rootItem()->appendChild(new TestResultItem(testResult)); return; } if (testResult->result() == Result::MessageDisabledTests) m_disabled += testResult->line(); m_testResultCount[testResult->result()]++; TestResultItem *newItem = new TestResultItem(testResult); TestResultItem *parentItem = findParentItemFor(newItem); 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 = static_cast(rootItem()->childAt(lastRow)); const TestResult *result = current->testResult(); if (result && result->result() == Result::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() { std::vector topLevelItems(rootItem()->begin(), rootItem()->end()); auto end = topLevelItems.rend(); for (auto it = topLevelItems.rbegin(); it != end; ++it) { TestResultItem *current = static_cast(*it); if (current->testResult()->result() == Result::MessageCurrentTest) { destroyItem(current); break; } } } 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 static_cast(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.width(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.width(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.width("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 = static_cast(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 = static_cast(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 << Result::Pass << Result::Fail << Result::ExpectedFail << Result::UnexpectedPass << Result::Skip << Result::MessageDebug << Result::MessageWarn << Result::MessageInternal << Result::MessageLocation << Result::MessageFatal << Result::Invalid << Result::BlacklistedPass << Result::BlacklistedFail << Result::Benchmark << Result::MessageIntermediate << Result::MessageCurrentTest << Result::MessageTestCaseStart << Result::MessageTestCaseSuccess << Result::MessageTestCaseSuccessWarn << Result::MessageTestCaseFail << Result::MessageTestCaseFailWarn << Result::MessageTestCaseEnd << Result::MessageInfo << Result::MessageSystem; invalidateFilter(); } void TestResultFilterModel::toggleTestResultType(Result::Type type) { if (m_enabled.contains(type)) { m_enabled.remove(type); if (type == Result::MessageInternal) m_enabled.remove(Result::MessageTestCaseEnd); if (type == Result::MessageDebug) m_enabled.remove(Result::MessageInfo); if (type == Result::MessageWarn) m_enabled.remove(Result::MessageSystem); } else { m_enabled.insert(type); if (type == Result::MessageInternal) m_enabled.insert(Result::MessageTestCaseEnd); if (type == Result::MessageDebug) m_enabled.insert(Result::MessageInfo); if (type == Result::MessageWarn) m_enabled.insert(Result::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; Result::Type resultType = m_sourceModel->testResult(index)->result(); switch (resultType) { case Result::MessageTestCaseSuccess: return m_enabled.contains(Result::Pass); case Result::MessageTestCaseFail: case Result::MessageTestCaseSuccessWarn: case Result::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); Result::Type type = m_sourceModel->testResult(child)->result(); if (type == Result::MessageTestCaseSuccess) type = Result::Pass; if (type == Result::MessageTestCaseFail || type == Result::MessageTestCaseFailWarn || type == Result::MessageTestCaseSuccessWarn) { if (acceptTestCaseResult(child)) return true; } else if (m_enabled.contains(type)) { return true; } } return false; } } // namespace Internal } // namespace Autotest