From 274d095f46fd85aecc56645bc7ce44ba0dd5a653 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 20 Aug 2015 15:59:15 +0200 Subject: Use QC's TreeModel for TestResultModel This changes the model to be a real tree instead of a list. Additionally the results pane now displays the results as tree as well. Change-Id: I69ba7bbfcd75ce17c3a0d4052498d9c1c7382d43 Reviewed-by: Niels Weber --- plugins/autotest/testresult.cpp | 20 +++ plugins/autotest/testresult.h | 6 + plugins/autotest/testresultdelegate.cpp | 28 ++- plugins/autotest/testresultdelegate.h | 25 ++- plugins/autotest/testresultmodel.cpp | 281 +++++++++++++++++++------------ plugins/autotest/testresultmodel.h | 42 ++--- plugins/autotest/testresultspane.cpp | 111 +++++++++--- plugins/autotest/testresultspane.h | 5 +- plugins/autotest/testrunner.cpp | 22 +-- plugins/autotest/testrunner.h | 2 +- plugins/autotest/testxmloutputreader.cpp | 38 +++-- plugins/autotest/testxmloutputreader.h | 2 +- 12 files changed, 369 insertions(+), 213 deletions(-) diff --git a/plugins/autotest/testresult.cpp b/plugins/autotest/testresult.cpp index 6a054a4c7d..0ce300eade 100644 --- a/plugins/autotest/testresult.cpp +++ b/plugins/autotest/testresult.cpp @@ -91,6 +91,16 @@ Result::Type TestResult::toResultType(int rt) return Result::MESSAGE_FATAL; case Result::MESSAGE_INTERNAL: return Result::MESSAGE_INTERNAL; + case Result::MESSAGE_TEST_CASE_START: + return Result::MESSAGE_TEST_CASE_START; + case Result::MESSAGE_TEST_CASE_SUCCESS: + return Result::MESSAGE_TEST_CASE_SUCCESS; + case Result::MESSAGE_TEST_CASE_WARN: + return Result::MESSAGE_TEST_CASE_WARN; + case Result::MESSAGE_TEST_CASE_FAIL: + return Result::MESSAGE_TEST_CASE_FAIL; + case Result::MESSAGE_TEST_CASE_END: + return Result::MESSAGE_TEST_CASE_END; case Result::MESSAGE_CURRENT_TEST: return Result::MESSAGE_CURRENT_TEST; default: @@ -120,6 +130,11 @@ QString TestResult::resultToString(const Result::Type type) case Result::MESSAGE_FATAL: return QLatin1String("FATAL"); case Result::MESSAGE_INTERNAL: + case Result::MESSAGE_TEST_CASE_START: + case Result::MESSAGE_TEST_CASE_SUCCESS: + case Result::MESSAGE_TEST_CASE_WARN: + case Result::MESSAGE_TEST_CASE_FAIL: + case Result::MESSAGE_TEST_CASE_END: case Result::MESSAGE_CURRENT_TEST: return QString(); case Result::BLACKLISTED_PASS: @@ -155,6 +170,11 @@ QColor TestResult::colorForType(const Result::Type type) case Result::MESSAGE_FATAL: return QColor("#640000"); case Result::MESSAGE_INTERNAL: + case Result::MESSAGE_TEST_CASE_START: + case Result::MESSAGE_TEST_CASE_SUCCESS: + case Result::MESSAGE_TEST_CASE_WARN: + case Result::MESSAGE_TEST_CASE_FAIL: + case Result::MESSAGE_TEST_CASE_END: case Result::MESSAGE_CURRENT_TEST: return QColor("transparent"); default: diff --git a/plugins/autotest/testresult.h b/plugins/autotest/testresult.h index c1d6d5eac4..e1e6e770d0 100644 --- a/plugins/autotest/testresult.h +++ b/plugins/autotest/testresult.h @@ -41,6 +41,11 @@ enum Type { MESSAGE_WARN, MESSAGE_FATAL, MESSAGE_INTERNAL, + MESSAGE_TEST_CASE_START, + MESSAGE_TEST_CASE_SUCCESS, + MESSAGE_TEST_CASE_WARN, + MESSAGE_TEST_CASE_FAIL, + MESSAGE_TEST_CASE_END, MESSAGE_CURRENT_TEST, UNKNOWN // ??? }; @@ -65,6 +70,7 @@ public: void setDescription(const QString &description) { m_description = description; } void setFileName(const QString &fileName) { m_file = fileName; } void setLine(int line) { m_line = line; } + void setResult(Result::Type type) { m_result = type; } static Result::Type resultFromString(const QString &resultString); static Result::Type toResultType(int rt); diff --git a/plugins/autotest/testresultdelegate.cpp b/plugins/autotest/testresultdelegate.cpp index 4dea7c050d..b123c99db9 100644 --- a/plugins/autotest/testresultdelegate.cpp +++ b/plugins/autotest/testresultdelegate.cpp @@ -37,11 +37,6 @@ TestResultDelegate::TestResultDelegate(QObject *parent) { } -TestResultDelegate::~TestResultDelegate() -{ - -} - QString TestResultDelegate::outputString(const TestResult &testResult, bool selected) { const QString desc = testResult.description(); @@ -88,7 +83,6 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->save(); QFontMetrics fm(opt.font); - QColor background; QColor foreground; const QAbstractItemView *view = qobject_cast(opt.widget); @@ -96,22 +90,23 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op if (selected) { painter->setBrush(opt.palette.highlight().color()); - background = opt.palette.highlight().color(); foreground = opt.palette.highlightedText().color(); } else { painter->setBrush(opt.palette.background().color()); - background = opt.palette.background().color(); foreground = opt.palette.text().color(); } - painter->setPen(Qt::NoPen); painter->drawRect(opt.rect); - painter->setPen(foreground); + TestResultFilterModel *resultFilterModel = static_cast(view->model()); - TestResultModel *resultModel = static_cast(resultFilterModel->sourceModel()); - LayoutPositions positions(opt, resultModel); - TestResult testResult = resultModel->testResult(resultFilterModel->mapToSource(index)); + LayoutPositions positions(opt, resultFilterModel); + const TestResult &testResult = resultFilterModel->testResult(index); + + // draw the indicator by ourself as we paint across it with the delegate + QStyleOptionViewItemV4 indicatorOpt = option; + indicatorOpt.rect = QRect(0, opt.rect.y(), positions.indentation(), opt.rect.height()); + opt.widget->style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOpt, painter); QIcon icon = index.data(Qt::DecorationRole).value(); if (!icon.isNull()) @@ -163,7 +158,7 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } painter->setClipRect(opt.rect); - painter->setPen(QColor::fromRgb(150, 150, 150)); + painter->setPen(opt.palette.midlight().color()); painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); painter->restore(); } @@ -181,13 +176,12 @@ QSize TestResultDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo QFontMetrics fm(opt.font); int fontHeight = fm.height(); TestResultFilterModel *resultFilterModel = static_cast(view->model()); - TestResultModel *resultModel = static_cast(resultFilterModel->sourceModel()); - LayoutPositions positions(opt, resultModel); + LayoutPositions positions(opt, resultFilterModel); QSize s; s.setWidth(opt.rect.width()); if (selected) { - TestResult testResult = resultModel->testResult(resultFilterModel->mapToSource(index)); + const TestResult &testResult = resultFilterModel->testResult(index); QString output = outputString(testResult, selected); output.replace(QLatin1Char('\n'), QChar::LineSeparator); diff --git a/plugins/autotest/testresultdelegate.h b/plugins/autotest/testresultdelegate.h index 63873a9d25..9df661ab5b 100644 --- a/plugins/autotest/testresultdelegate.h +++ b/plugins/autotest/testresultdelegate.h @@ -33,7 +33,6 @@ class TestResultDelegate : public QStyledItemDelegate Q_OBJECT public: explicit TestResultDelegate(QObject *parent = 0); - ~TestResultDelegate(); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; @@ -55,15 +54,19 @@ private: class LayoutPositions { public: - LayoutPositions(QStyleOptionViewItemV4 &options, TestResultModel *model) + LayoutPositions(QStyleOptionViewItemV4 &options, TestResultFilterModel *filterModel) : m_totalWidth(options.rect.width()), - m_maxFileLength(model->maxWidthOfFileName(options.font)), - m_maxLineLength(model->maxWidthOfLineNumber(options.font)), - m_realFileLength(m_maxFileLength), m_top(options.rect.top()), m_bottom(options.rect.bottom()) { + TestResultModel *srcModel = static_cast(filterModel->sourceModel()); + m_maxFileLength = srcModel->maxWidthOfFileName(options.font); + m_maxLineLength = srcModel->maxWidthOfLineNumber(options.font); + m_realFileLength = m_maxFileLength; m_typeAreaWidth = QFontMetrics(options.font).width(QLatin1String("XXXXXXXX")); + m_indentation = options.widget ? options.widget->style()->pixelMetric( + QStyle::PM_TreeViewIndentation, &options) : 0; + m_level = srcModel->hasChildren(filterModel->mapToSource(options.index)) ? 1 : 2; int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; if (m_maxFileLength > flexibleArea / 2) m_realFileLength = flexibleArea / 2; @@ -71,7 +74,7 @@ private: } int top() const { return m_top + ITEM_MARGIN; } - int left() const { return ITEM_MARGIN; } + int left() const { return ITEM_MARGIN + m_indentation * m_level; } int right() const { return m_totalWidth - ITEM_MARGIN; } int bottom() const { return m_bottom; } int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; } @@ -80,17 +83,19 @@ private: int fontHeight() const { return m_fontHeight; } int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; } int typeAreaWidth() const { return m_typeAreaWidth; } - int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; } + int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING + + (1 - m_level) * m_indentation; } int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); } int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; } int lineAreaLeft() const { return right() - m_maxLineLength; } + int indentation() const { return m_indentation; } QRect typeArea() const { return QRect(typeAreaLeft(), top(), typeAreaWidth(), m_fontHeight); } QRect textArea() const { return QRect(textAreaLeft(), top(), - fileAreaLeft() - ITEM_SPACING, m_fontHeight); } + textAreaWidth(), m_fontHeight); } QRect fileArea() const { return QRect(fileAreaLeft(), top(), - lineAreaLeft() - ITEM_SPACING, m_fontHeight); } + m_realFileLength + ITEM_SPACING, m_fontHeight); } QRect lineArea() const { return QRect(lineAreaLeft(), top(), m_maxLineLength, m_fontHeight); } @@ -104,6 +109,8 @@ private: int m_bottom; int m_fontHeight; int m_typeAreaWidth; + int m_level; + int m_indentation; static const int ICON_SIZE = 16; static const int ITEM_MARGIN = 2; diff --git a/plugins/autotest/testresultmodel.cpp b/plugins/autotest/testresultmodel.cpp index ee10470032..dd46554043 100644 --- a/plugins/autotest/testresultmodel.cpp +++ b/plugins/autotest/testresultmodel.cpp @@ -19,47 +19,22 @@ #include "testresultmodel.h" -#include #include #include -#include namespace Autotest { namespace Internal { -TestResultModel::TestResultModel(QObject *parent) : - QAbstractItemModel(parent), - m_widthOfLineNumber(0), - m_maxWidthOfFileName(0), - m_lastMaxWidthIndex(0) -{ -} - -TestResultModel::~TestResultModel() -{ - m_testResults.clear(); -} - -QModelIndex TestResultModel::index(int row, int column, const QModelIndex &parent) const -{ - if (parent.isValid()) - return QModelIndex(); - return createIndex(row, column); -} +/********************************* TestResultItem ******************************************/ -QModelIndex TestResultModel::parent(const QModelIndex &) const +TestResultItem::TestResultItem(TestResult *testResult) + : m_testResult(testResult) { - return QModelIndex(); } -int TestResultModel::rowCount(const QModelIndex &parent) const +TestResultItem::~TestResultItem() { - return parent.isValid() ? 0 : m_testResults.size(); -} - -int TestResultModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 1; + delete m_testResult; } static QIcon testResultIcon(Result::Type result) { @@ -77,116 +52,201 @@ static QIcon testResultIcon(Result::Type result) { QIcon(QLatin1String(":/images/fatal.png")), }; // provide an icon for unknown?? - if (result < 0 || result >= Result::MESSAGE_INTERNAL) - return QIcon(); + if (result < 0 || result >= Result::MESSAGE_INTERNAL) { + switch (result) { + case Result::MESSAGE_TEST_CASE_SUCCESS: + return icons[Result::PASS]; + case Result::MESSAGE_TEST_CASE_FAIL: + return icons[Result::FAIL]; + case Result::MESSAGE_TEST_CASE_WARN: + return icons[Result::MESSAGE_WARN]; + default: + return QIcon(); + } + } return icons[result]; } -QVariant TestResultModel::data(const QModelIndex &index, int role) const +QVariant TestResultItem::data(int column, int role) const { - if (!index.isValid() || index.row() >= m_testResults.count() || index.column() != 0) - return QVariant(); - if (role == Qt::DisplayRole) { - const TestResult &tr = m_testResults.at(index.row()); - switch (tr.result()) { - case Result::PASS: - case Result::FAIL: - case Result::EXPECTED_FAIL: - case Result::UNEXPECTED_PASS: - case Result::SKIP: - case Result::BLACKLISTED_PASS: - case Result::BLACKLISTED_FAIL: - case Result::BENCHMARK: - return QString::fromLatin1("%1::%2 (%3) - %4").arg(tr.className(), tr.testCase(), - tr.dataTag(), tr.fileName()); - default: - return tr.description(); + if (role == Qt::DecorationRole) + return m_testResult ? testResultIcon(m_testResult->result()) : QVariant(); + + return Utils::TreeItem::data(column, role); +} + +void TestResultItem::updateDescription(const QString &description) +{ + QTC_ASSERT(m_testResult, return); + + m_testResult->setDescription(description); +} + +void TestResultItem::updateResult() +{ + if (m_testResult->result() != Result::MESSAGE_TEST_CASE_START) + return; + + Result::Type newResult = Result::MESSAGE_TEST_CASE_SUCCESS; + foreach (Utils::TreeItem *child, children()) { + const TestResult *current = static_cast(child)->testResult(); + if (current) { + switch (current->result()) { + case Result::FAIL: + case Result::MESSAGE_FATAL: + case Result::UNEXPECTED_PASS: + m_testResult->setResult(Result::MESSAGE_TEST_CASE_FAIL); + return; + case Result::EXPECTED_FAIL: + case Result::MESSAGE_WARN: + case Result::SKIP: + case Result::BLACKLISTED_FAIL: + case Result::BLACKLISTED_PASS: + newResult = Result::MESSAGE_TEST_CASE_WARN; + break; + default: {} + } } } - if (role == Qt::DecorationRole) { - const TestResult &tr = m_testResults.at(index.row()); - return testResultIcon(tr.result()); - } + m_testResult->setResult(newResult); +} - return QVariant(); +/********************************* TestResultModel *****************************************/ + +TestResultModel::TestResultModel(QObject *parent) + : Utils::TreeModel(parent), + m_widthOfLineNumber(0), + m_maxWidthOfFileName(0) +{ } -void TestResultModel::addTestResult(const TestResult &testResult) +QVariant TestResultModel::data(const QModelIndex &idx, int role) const { - const bool isCurrentTestMssg = testResult.result() == Result::MESSAGE_CURRENT_TEST; - TestResult lastMssg = m_testResults.empty() ? TestResult() : m_testResults.last(); + if (!idx.isValid()) + return QVariant(); + + if (role == Qt::DecorationRole) + return itemForIndex(idx)->data(0, Qt::DecorationRole); + + return QVariant(); +} - int position = m_testResults.size(); +void TestResultModel::addTestResult(TestResult *testResult, bool autoExpand) +{ + const bool isCurrentTestMssg = testResult->result() == Result::MESSAGE_CURRENT_TEST; - if (isCurrentTestMssg && lastMssg.result() == Result::MESSAGE_CURRENT_TEST) { - lastMssg.setDescription(testResult.description()); - m_testResults.replace(m_testResults.size() - 1, lastMssg); - const QModelIndex changed = index(m_testResults.size() - 1, 0, QModelIndex()); - emit dataChanged(changed, changed); + QVector topLevelItems = rootItem()->children(); + int lastRow = topLevelItems.size() - 1; + TestResultItem *newItem = new TestResultItem(testResult); + // we'll add the new item, so raising it's counter + if (!isCurrentTestMssg) { + int count = m_testResultCount.value(testResult->result(), 0); + m_testResultCount.insert(testResult->result(), ++count); } else { - if (!isCurrentTestMssg && position) // decrement only if at least one other item - --position; - beginInsertRows(QModelIndex(), position, position); - m_testResults.insert(position, testResult); - endInsertRows(); + // MESSAGE_CURRENT_TEST should always be the last top level item + if (lastRow >= 0) { + TestResultItem *current = static_cast(topLevelItems.at(lastRow)); + const TestResult *result = current->testResult(); + if (result && result->result() == Result::MESSAGE_CURRENT_TEST) { + current->updateDescription(testResult->description()); + emit dataChanged(current->index(), current->index()); + return; + } + } + + rootItem()->appendChild(newItem); + return; } - if (!isCurrentTestMssg) { - int count = m_testResultCount.value(testResult.result(), 0); - m_testResultCount.insert(testResult.result(), ++count); + // FIXME this might be totally wrong... we need some more unique information! + for (int row = lastRow; row >= 0; --row) { + TestResultItem *current = static_cast(topLevelItems.at(row)); + const TestResult *result = current->testResult(); + if (result && result->className() == testResult->className()) { + current->appendChild(newItem); + if (autoExpand) + current->expand(); + if (testResult->result() == Result::MESSAGE_TEST_CASE_END) { + current->updateResult(); + emit dataChanged(current->index(), current->index()); + } + return; + } } + // if we have a MESSAGE_CURRENT_TEST present, add the new top level item before it + if (lastRow >= 0) { + TestResultItem *current = static_cast(topLevelItems.at(lastRow)); + const TestResult *result = current->testResult(); + if (result && result->result() == Result::MESSAGE_CURRENT_TEST) { + rootItem()->insertChild(current->index().row(), newItem); + return; + } + } + + rootItem()->appendChild(newItem); } void TestResultModel::removeCurrentTestMessage() { - TestResult lastMssg = m_testResults.empty() ? TestResult() : m_testResults.last(); - if (lastMssg.result() == Result::MESSAGE_CURRENT_TEST) { - beginRemoveRows(QModelIndex(), m_testResults.size() - 1, m_testResults.size() - 1); - m_testResults.removeLast(); - endRemoveRows(); + QVector topLevelItems = rootItem()->children(); + for (int row = topLevelItems.size() - 1; row >= 0; --row) { + TestResultItem *current = static_cast(topLevelItems.at(row)); + if (current->testResult()->result() == Result::MESSAGE_CURRENT_TEST) { + delete takeItem(current); + break; + } } } void TestResultModel::clearTestResults() { - if (m_testResults.size() == 0) - return; - beginRemoveRows(QModelIndex(), 0, m_testResults.size() - 1); - m_testResults.clear(); + clear(); m_testResultCount.clear(); - m_lastMaxWidthIndex = 0; + m_processedIndices.clear(); m_maxWidthOfFileName = 0; m_widthOfLineNumber = 0; - endRemoveRows(); } -TestResult TestResultModel::testResult(const QModelIndex &index) const +TestResult TestResultModel::testResult(const QModelIndex &idx) { - if (!index.isValid()) - return TestResult(QString(), QString()); - return m_testResults.at(index.row()); + if (idx.isValid()) + return *(static_cast(itemForIndex(idx))->testResult()); + + return TestResult(); } int TestResultModel::maxWidthOfFileName(const QFont &font) { - int count = m_testResults.size(); - if (count == 0) - return 0; - if (m_maxWidthOfFileName > 0 && font == m_measurementFont && m_lastMaxWidthIndex == count - 1) - return m_maxWidthOfFileName; - - QFontMetrics fm(font); - m_measurementFont = font; - - for (int i = m_lastMaxWidthIndex; i < count; ++i) { - QString filename = m_testResults.at(i).fileName(); - const int pos = filename.lastIndexOf(QLatin1Char('/')); - if (pos != -1) - filename = filename.mid(pos +1); - - m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(filename)); + if (font != m_measurementFont) { + m_processedIndices.clear(); + m_maxWidthOfFileName = 0; + m_measurementFont = font; + } + + const QFontMetrics fm(font); + const QVector &topLevelItems = rootItem()->children(); + const int count = topLevelItems.size(); + for (int row = 0; row < count; ++row) { + int processed = row < m_processedIndices.size() ? m_processedIndices.at(row) : 0; + const QVector &children = topLevelItems.at(row)->children(); + const int itemCount = children.size(); + if (processed < itemCount) { + for (int childRow = processed; childRow < itemCount; ++childRow) { + const TestResultItem *item = static_cast(children.at(childRow)); + if (const TestResult *result = item->testResult()) { + QString fileName = result->fileName(); + const int pos = fileName.lastIndexOf(QLatin1Char('/')); + if (pos != -1) + fileName = fileName.mid(pos + 1); + m_maxWidthOfFileName = qMax(m_maxWidthOfFileName, fm.width(fileName)); + } + } + if (row < m_processedIndices.size()) + m_processedIndices.replace(row, itemCount); + else + m_processedIndices.insert(row, itemCount); + } } - m_lastMaxWidthIndex = count - 1; return m_maxWidthOfFileName; } @@ -200,11 +260,6 @@ int TestResultModel::maxWidthOfLineNumber(const QFont &font) return m_widthOfLineNumber; } -int TestResultModel::resultTypeCount(Result::Type type) -{ - return m_testResultCount.value(type, 0); -} - /********************************** Filter Model **********************************/ TestResultFilterModel::TestResultFilterModel(TestResultModel *sourceModel, QObject *parent) @@ -222,7 +277,9 @@ void TestResultFilterModel::enableAllResultTypes() << Result::MESSAGE_WARN << Result::MESSAGE_INTERNAL << Result::MESSAGE_FATAL << Result::UNKNOWN << Result::BLACKLISTED_PASS << Result::BLACKLISTED_FAIL << Result::BENCHMARK - << Result::MESSAGE_CURRENT_TEST; + << Result::MESSAGE_CURRENT_TEST << Result::MESSAGE_TEST_CASE_START + << Result::MESSAGE_TEST_CASE_SUCCESS << Result::MESSAGE_TEST_CASE_WARN + << Result::MESSAGE_TEST_CASE_FAIL << Result::MESSAGE_TEST_CASE_END; invalidateFilter(); } @@ -230,8 +287,12 @@ void TestResultFilterModel::toggleTestResultType(Result::Type type) { if (m_enabled.contains(type)) { m_enabled.remove(type); + if (type == Result::MESSAGE_INTERNAL) + m_enabled.remove(Result::MESSAGE_TEST_CASE_END); } else { m_enabled.insert(type); + if (type == Result::MESSAGE_INTERNAL) + m_enabled.insert(Result::MESSAGE_TEST_CASE_END); } invalidateFilter(); } diff --git a/plugins/autotest/testresultmodel.h b/plugins/autotest/testresultmodel.h index 97f3a3a53c..5a108aa45c 100644 --- a/plugins/autotest/testresultmodel.h +++ b/plugins/autotest/testresultmodel.h @@ -27,43 +27,47 @@ #include #include +#include + namespace Autotest { namespace Internal { -class TestResultModel : public QAbstractItemModel +class TestResultItem : public Utils::TreeItem +{ +public: + explicit TestResultItem(TestResult *testResult); + ~TestResultItem(); + QVariant data(int column, int role) const; + const TestResult *testResult() const { return m_testResult; } + void updateDescription(const QString &description); + void updateResult(); + +private: + TestResult *m_testResult; +}; + +class TestResultModel : public Utils::TreeModel { - Q_OBJECT public: explicit TestResultModel(QObject *parent = 0); - ~TestResultModel(); - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &) const; - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; - - void addTestResult(const TestResult &testResult); + QVariant data(const QModelIndex &idx, int role) const; + + void addTestResult(TestResult *testResult, bool autoExpand = false); void removeCurrentTestMessage(); void clearTestResults(); - bool hasResults() const { return m_testResults.size() > 0; } - TestResult testResult(const QModelIndex &index) const; + TestResult testResult(const QModelIndex &idx); int maxWidthOfFileName(const QFont &font); int maxWidthOfLineNumber(const QFont &font); - int resultTypeCount(Result::Type type); - -signals: - -public slots: + int resultTypeCount(Result::Type type) const { return m_testResultCount.value(type, 0); } private: - QList m_testResults; QMap m_testResultCount; int m_widthOfLineNumber; int m_maxWidthOfFileName; - int m_lastMaxWidthIndex; + QList m_processedIndices; QFont m_measurementFont; }; diff --git a/plugins/autotest/testresultspane.cpp b/plugins/autotest/testresultspane.cpp index 8f3d4f5b82..c971321333 100644 --- a/plugins/autotest/testresultspane.cpp +++ b/plugins/autotest/testresultspane.cpp @@ -114,6 +114,9 @@ TestResultsPane::TestResultsPane(QObject *parent) : connect(m_treeView, &ResultsTreeView::copyShortcutTriggered, [this] () { onCopyItemTriggered(m_treeView->currentIndex()); }); + connect(m_model, &TestResultModel::requestExpansion, [this] (QModelIndex idx) { + m_treeView->expand(m_filterModel->mapFromSource(idx)); + }); connect(TestRunner::instance(), &TestRunner::testRunStarted, this, &TestResultsPane::onTestRunStarted); connect(TestRunner::instance(), &TestRunner::testRunFinished, @@ -122,6 +125,18 @@ TestResultsPane::TestResultsPane(QObject *parent) : void TestResultsPane::createToolButtons() { + m_expandCollapse = new QToolButton(m_treeView); + m_expandCollapse->setIcon(QIcon(QLatin1String(":/find/images/expand.png"))); + m_expandCollapse->setToolTip(tr("Expand All")); + m_expandCollapse->setCheckable(true); + m_expandCollapse->setChecked(false); + connect(m_expandCollapse, &QToolButton::clicked, [this] (bool checked) { + if (checked) + m_treeView->expandAll(); + else + m_treeView->collapseAll(); + }); + m_runAll = new QToolButton(m_treeView); m_runAll->setIcon(QIcon(QLatin1String(":/images/run.png"))); m_runAll->setToolTip(tr("Run All Tests")); @@ -167,12 +182,12 @@ TestResultsPane::~TestResultsPane() m_instance = 0; } -void TestResultsPane::addTestResult(const TestResult &result) +void TestResultsPane::addTestResult(TestResult *result) { const QScrollBar *scrollBar = m_treeView->verticalScrollBar(); m_atEnd = scrollBar ? scrollBar->value() == scrollBar->maximum() : true; - m_model->addTestResult(result); + m_model->addTestResult(result, m_expandCollapse->isChecked()); if (!m_treeView->isVisible()) popup(Core::IOutputPane::NoModeSwitch); flash(); @@ -191,7 +206,8 @@ QWidget *TestResultsPane::outputWidget(QWidget *parent) QList TestResultsPane::toolBarWidgets() const { - return QList() << m_runAll << m_runSelected << m_stopTestRun << m_filterButton; + return QList() << m_expandCollapse << m_runAll << m_runSelected << m_stopTestRun + << m_filterButton; } QString TestResultsPane::displayName() const @@ -266,17 +282,41 @@ void TestResultsPane::goToNext() if (!canNext()) return; - QModelIndex currentIndex = m_treeView->currentIndex(); + const QModelIndex currentIndex = m_treeView->currentIndex(); + QModelIndex nextCurrentIndex; + if (currentIndex.isValid()) { - int row = currentIndex.row() + 1; - if (row == m_filterModel->rowCount(QModelIndex())) - row = 0; - currentIndex = m_filterModel->index(row, 0, QModelIndex()); - } else { - currentIndex = m_filterModel->index(0, 0, QModelIndex()); + // try to set next to first child or next sibling + if (m_filterModel->rowCount(currentIndex)) { + nextCurrentIndex = currentIndex.child(0, 0); + } else { + nextCurrentIndex = currentIndex.sibling(currentIndex.row() + 1, 0); + // if it had no sibling check siblings of parent (and grandparents if necessary) + if (!nextCurrentIndex.isValid()) { + + QModelIndex parent = currentIndex.parent(); + do { + if (!parent.isValid()) + break; + nextCurrentIndex = parent.sibling(parent.row() + 1, 0); + parent = parent.parent(); + } while (!nextCurrentIndex.isValid()); + } + } + } + + // if we have no current or could not find a next one, use the first item of the whole tree + if (!nextCurrentIndex.isValid()) { + Utils::TreeItem *rootItem = m_model->itemForIndex(QModelIndex()); + // if the tree does not contain any item - don't do anything + if (!rootItem || !rootItem->childCount()) + return; + + nextCurrentIndex = m_filterModel->mapFromSource(m_model->indexForItem(rootItem->child(0))); } - m_treeView->setCurrentIndex(currentIndex); - onItemActivated(currentIndex); + + m_treeView->setCurrentIndex(nextCurrentIndex); + onItemActivated(nextCurrentIndex); } void TestResultsPane::goToPrev() @@ -284,17 +324,37 @@ void TestResultsPane::goToPrev() if (!canPrevious()) return; - QModelIndex currentIndex = m_treeView->currentIndex(); + const QModelIndex currentIndex = m_treeView->currentIndex(); + QModelIndex nextCurrentIndex; + if (currentIndex.isValid()) { - int row = currentIndex.row() - 1; - if (row < 0) - row = m_filterModel->rowCount(QModelIndex()) - 1; - currentIndex = m_filterModel->index(row, 0, QModelIndex()); - } else { - currentIndex = m_filterModel->index(m_filterModel->rowCount(QModelIndex()) - 1, 0, QModelIndex()); + // try to set next to prior sibling or parent + if (currentIndex.row() > 0) { + nextCurrentIndex = currentIndex.sibling(currentIndex.row() - 1, 0); + // if the sibling has children, use the last one + while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) + nextCurrentIndex = nextCurrentIndex.child(rowCount - 1, 0); + } else { + nextCurrentIndex = currentIndex.parent(); + } } - m_treeView->setCurrentIndex(currentIndex); - onItemActivated(currentIndex); + + // if we have no current or didn't find a sibling/parent use the last item of the whole tree + if (!nextCurrentIndex.isValid()) { + const QModelIndex rootIdx = m_filterModel->index(0, 0); + // if the tree does not contain any item - don't do anything + if (!rootIdx.isValid()) + return; + + // get the last (visible) top level index + nextCurrentIndex = m_filterModel->index(m_filterModel->rowCount(QModelIndex()) - 1, 0); + // step through until end + while (int rowCount = m_filterModel->rowCount(nextCurrentIndex)) + nextCurrentIndex = nextCurrentIndex.child(rowCount - 1, 0); + } + + m_treeView->setCurrentIndex(nextCurrentIndex); + onItemActivated(nextCurrentIndex); } void TestResultsPane::onItemActivated(const QModelIndex &index) @@ -482,14 +542,15 @@ void TestResultsPane::onSaveWholeTriggered() } // helper for onCopyWholeTriggered() and onSaveWholeTriggered() -QString TestResultsPane::getWholeOutput() +QString TestResultsPane::getWholeOutput(const QModelIndex &parent) { QString output; - const QModelIndex invalid; // practically the invisible root item - for (int row = 0, count = m_model->rowCount(invalid); row < count; ++row) { - const TestResult result = m_model->testResult(m_model->index(row, 0, invalid)); + for (int row = 0, count = m_model->rowCount(parent); row < count; ++row) { + QModelIndex current = m_model->index(row, 0, parent); + const TestResult result = m_model->testResult(current); output.append(TestResult::resultToString(result.result())).append(QLatin1Char('\t')); output.append(TestResultDelegate::outputString(result, true)).append(QLatin1Char('\n')); + output.append(getWholeOutput(current)); } return output; } diff --git a/plugins/autotest/testresultspane.h b/plugins/autotest/testresultspane.h index 1bb3cddedd..5fbb71413e 100644 --- a/plugins/autotest/testresultspane.h +++ b/plugins/autotest/testresultspane.h @@ -84,7 +84,7 @@ public: signals: public slots: - void addTestResult(const TestResult &result); + void addTestResult(TestResult *result); private slots: void onItemActivated(const QModelIndex &index); @@ -106,7 +106,7 @@ private: void onCopyItemTriggered(const QModelIndex &idx); void onCopyWholeTriggered(); void onSaveWholeTriggered(); - QString getWholeOutput(); + QString getWholeOutput(const QModelIndex &parent = QModelIndex()); QWidget *m_outputWidget; QFrame *m_summaryWidget; @@ -115,6 +115,7 @@ private: TestResultModel *m_model; TestResultFilterModel *m_filterModel; Core::IContext *m_context; + QToolButton *m_expandCollapse; QToolButton *m_runAll; QToolButton *m_runSelected; QToolButton *m_stopTestRun; diff --git a/plugins/autotest/testrunner.cpp b/plugins/autotest/testrunner.cpp index b556989d62..ea26b0e75f 100644 --- a/plugins/autotest/testrunner.cpp +++ b/plugins/autotest/testrunner.cpp @@ -43,7 +43,7 @@ namespace Internal { static TestRunner *m_instance = 0; -void emitTestResultCreated(const TestResult &testResult) +void emitTestResultCreated(TestResult *testResult) { emit m_instance->testResultCreated(testResult); } @@ -114,7 +114,7 @@ void performTestRun(QFutureInterface &futureInterface, if (config->project()) { testCaseCount += config->testCaseCount(); } else { - emitTestResultCreated(FaultyTestResult(Result::MESSAGE_WARN, + emitTestResultCreated(new FaultyTestResult(Result::MESSAGE_WARN, QObject::tr("Project is null for \"%1\". Removing from test run.\n" "Check the test environment.").arg(config->displayName()))); } @@ -148,7 +148,7 @@ void performTestRun(QFutureInterface &futureInterface, QProcessEnvironment environment = testConfiguration->environment().toProcessEnvironment(); QString commandFilePath = executableFilePath(testConfiguration->targetFile(), environment); if (commandFilePath.isEmpty()) { - emitTestResultCreated(FaultyTestResult(Result::MESSAGE_FATAL, + emitTestResultCreated(new FaultyTestResult(Result::MESSAGE_FATAL, QObject::tr("Could not find command \"%1\". (%2)") .arg(testConfiguration->targetFile()) .arg(testConfiguration->displayName()))); @@ -177,8 +177,8 @@ void performTestRun(QFutureInterface &futureInterface, if (futureInterface.isCanceled()) { testProcess.kill(); testProcess.waitForFinished(); - emitTestResultCreated(FaultyTestResult(Result::MESSAGE_FATAL, - QObject::tr("Test run canceled by user."))); + emitTestResultCreated(new FaultyTestResult(Result::MESSAGE_FATAL, + QObject::tr("Test run canceled by user."))); } qApp->processEvents(); } @@ -188,7 +188,7 @@ void performTestRun(QFutureInterface &futureInterface, if (testProcess.state() != QProcess::NotRunning) { testProcess.kill(); testProcess.waitForFinished(); - emitTestResultCreated(FaultyTestResult(Result::MESSAGE_FATAL, QObject::tr( + emitTestResultCreated(new FaultyTestResult(Result::MESSAGE_FATAL, QObject::tr( "Test case canceled due to timeout. \nMaybe raise the timeout?"))); } } @@ -208,14 +208,14 @@ void TestRunner::prepareToRunTests() foreach (TestConfiguration *config, m_selectedTests) { if (!omitRunConfigWarnings && config->guessedConfiguration()) { - TestResultsPane::instance()->addTestResult(FaultyTestResult(Result::MESSAGE_WARN, + TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MESSAGE_WARN, tr("Project's run configuration was guessed for \"%1\".\n" "This might cause trouble during execution.").arg(config->displayName()))); } } if (m_selectedTests.empty()) { - TestResultsPane::instance()->addTestResult(FaultyTestResult(Result::MESSAGE_WARN, + TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MESSAGE_WARN, tr("No tests selected. Canceling test run."))); onFinished(); return; @@ -223,7 +223,7 @@ void TestRunner::prepareToRunTests() ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); if (!project) { - TestResultsPane::instance()->addTestResult(FaultyTestResult(Result::MESSAGE_WARN, + TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MESSAGE_WARN, tr("Project is null. Canceling test run.\n" "Only desktop kits are supported. Make sure the " "currently active kit is a desktop kit."))); @@ -239,7 +239,7 @@ void TestRunner::prepareToRunTests() if (project->hasActiveBuildSettings()) { buildProject(project); } else { - TestResultsPane::instance()->addTestResult(FaultyTestResult(Result::MESSAGE_FATAL, + TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MESSAGE_FATAL, tr("Project is not configured. Canceling test run."))); onFinished(); return; @@ -284,7 +284,7 @@ void TestRunner::buildFinished(bool success) if (success) { runTests(); } else { - TestResultsPane::instance()->addTestResult(FaultyTestResult(Result::MESSAGE_FATAL, + TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MESSAGE_FATAL, tr("Build failed. Canceling test run."))); onFinished(); } diff --git a/plugins/autotest/testrunner.h b/plugins/autotest/testrunner.h index 92de02f67b..e03a0c3d4d 100644 --- a/plugins/autotest/testrunner.h +++ b/plugins/autotest/testrunner.h @@ -47,7 +47,7 @@ public: signals: void testRunStarted(); void testRunFinished(); - void testResultCreated(const TestResult &testResult); + void testResultCreated(TestResult *testResult); void requestStopTestRun(); public slots: diff --git a/plugins/autotest/testxmloutputreader.cpp b/plugins/autotest/testxmloutputreader.cpp index ae6c223f93..f137b8d1d6 100644 --- a/plugins/autotest/testxmloutputreader.cpp +++ b/plugins/autotest/testxmloutputreader.cpp @@ -208,8 +208,12 @@ void TestXmlOutputReader::processOutput() while (m_testApplication->canReadLine()) { // TODO Qt5 uses UTF-8 - while Qt4 uses ISO-8859-1 - could this be a problem? const QString line = QString::fromUtf8(m_testApplication->readLine()).trimmed(); - if (line.isEmpty() || xmlStartsWith(line, QLatin1String(""))) { - TestResult testResult(className, testCase, dataTag, result, description); + TestResult *testResult = new TestResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) file = constructSourceFilePath(m_testApplication->workingDirectory(), file, m_testApplication->program()); - testResult.setFileName(file); - testResult.setLine(lineNumber); + testResult->setFileName(file); + testResult->setLine(lineNumber); testResultCreated(testResult); } continue; } if (xmlExtractBenchmarkInformation(line, QLatin1String("") || line == QLatin1String("")) { - TestResult testResult(className, testCase, dataTag, result, description); + TestResult *testResult = new TestResult(className, testCase, dataTag, result, description); if (!file.isEmpty()) file = constructSourceFilePath(m_testApplication->workingDirectory(), file, m_testApplication->program()); - testResult.setFileName(file); - testResult.setLine(lineNumber); + testResult->setFileName(file); + testResult->setLine(lineNumber); testResultCreated(testResult); description = QString(); } else if (line == QLatin1String("") && !duration.isEmpty()) { - TestResult testResult(className, testCase, QString(), Result::MESSAGE_INTERNAL, - QObject::tr("Execution took %1 ms.").arg(duration)); - testResultCreated(testResult); + testResultCreated(new TestResult(className, testCase, QString(), Result::MESSAGE_INTERNAL, + QObject::tr("Execution took %1 ms.").arg(duration))); emit increaseProgress(); } else if (line == QLatin1String("") && !duration.isEmpty()) { - TestResult testResult(className, QString(), QString(), Result::MESSAGE_INTERNAL, - QObject::tr("Test execution took %1 ms.").arg(duration)); - testResultCreated(testResult); + testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_TEST_CASE_END, + QObject::tr("Test execution took %1 ms.").arg(duration))); } else if (readingDescription) { if (line.endsWith(QLatin1String("]]>"))) { description.append(QLatin1Char('\n')); @@ -283,10 +285,10 @@ void TestXmlOutputReader::processOutput() description.append(line); } } else if (xmlStartsWith(line, QLatin1String(""), qtVersion)) { - testResultCreated(FaultyTestResult(Result::MESSAGE_INTERNAL, + testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL, QObject::tr("Qt version: %1").arg(qtVersion))); } else if (xmlStartsWith(line, QLatin1String(""), qtestVersion)) { - testResultCreated(FaultyTestResult(Result::MESSAGE_INTERNAL, + testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL, QObject::tr("QTest version: %1").arg(qtestVersion))); } else { // qDebug() << "Unhandled line:" << line; // TODO remove diff --git a/plugins/autotest/testxmloutputreader.h b/plugins/autotest/testxmloutputreader.h index 1ca22b9698..6dfcef51cc 100644 --- a/plugins/autotest/testxmloutputreader.h +++ b/plugins/autotest/testxmloutputreader.h @@ -45,7 +45,7 @@ public: public slots: void processOutput(); signals: - void testResultCreated(const TestResult &testResult); + void testResultCreated(TestResult *testResult); void increaseProgress(); private: QProcess *m_testApplication; -- cgit v1.2.1