diff --git a/src/plugins/autotest/gtest/gtestoutputreader.cpp b/src/plugins/autotest/gtest/gtestoutputreader.cpp index c0c08196ada..8e4fcd5a766 100644 --- a/src/plugins/autotest/gtest/gtestoutputreader.cpp +++ b/src/plugins/autotest/gtest/gtestoutputreader.cpp @@ -28,12 +28,12 @@ GTestOutputReader::GTestOutputReader(Process *testApplication, void GTestOutputReader::processOutputLine(const QByteArray &outputLine) { static const QRegularExpression newTestStarts("^\\[-{10}\\] \\d+ tests? from (.*)$"); - static const QRegularExpression testEnds("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$"); + static const QRegularExpression testEnds("^\\[-{10}\\] \\d+ tests? from (.*) \\(((\\d+) .*)\\)$"); static const QRegularExpression newTestSetStarts("^\\[ RUN \\] (.*)$"); static const QRegularExpression testSetSuccess("^\\[ OK \\] (.*) \\((.*)\\)$"); - static const QRegularExpression testSetFail("^\\[ FAILED \\] (.*) \\((\\d+ ms)\\)$"); + static const QRegularExpression testSetFail("^\\[ FAILED \\] (.*) \\(((\\d+) ms)\\)$"); static const QRegularExpression testDeath("^\\[ DEATH \\] (.*)$"); - static const QRegularExpression testSetSkipped("^\\[ SKIPPED \\] (.*) \\((\\d+ ms)\\)$"); + static const QRegularExpression testSetSkipped("^\\[ SKIPPED \\] (.*) \\(((\\d+) ms)\\)$"); static const QRegularExpression disabledTests("^ YOU HAVE (\\d+) DISABLED TESTS?$"); static const QRegularExpression iterations("^Repeating all tests " "\\(iteration (\\d+)\\) \\. \\. \\.$"); @@ -70,6 +70,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine) TestResult testResult = createDefaultResult(); testResult.setResult(ResultType::TestEnd); testResult.setDescription(Tr::tr("Test execution took %1.").arg(match.captured(2))); + testResult.setDuration(match.captured(3)); reportResult(testResult); m_currentTestSuite.clear(); m_currentTestCase.clear(); @@ -102,6 +103,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine) testResult = createDefaultResult(); testResult.setResult(ResultType::MessageInternal); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); + testResult.setDuration(match.captured(3)); reportResult(testResult); // TODO: bump progress? } else if (ExactMatch match = testSetFail.match(line)) { @@ -127,6 +129,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine) testResult = createDefaultResult(); testResult.setResult(ResultType::MessageInternal); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); + testResult.setDuration(match.captured(3)); reportResult(testResult); } else if (ExactMatch match = logging.match(line)) { const QString severity = match.captured(1).trimmed(); diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp index 682d0323f17..9a5eab32996 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.cpp +++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp @@ -475,6 +475,8 @@ void QtTestOutputReader::sendCompleteInformation() } } testResult.setDescription(m_description); + if (!m_duration.isEmpty()) + testResult.setDuration(m_duration); reportResult(testResult); } @@ -510,6 +512,7 @@ void QtTestOutputReader::sendFinishMessage(bool isFunction) if (!m_duration.isEmpty()) { result.setDescription(isFunction ? Tr::tr("Execution took %1 ms.").arg(m_duration) : Tr::tr("Test execution took %1 ms.").arg(m_duration)); + result.setDuration(m_duration); } else { result.setDescription(isFunction ? Tr::tr("Test function finished.") : Tr::tr("Test finished.")); diff --git a/src/plugins/autotest/testresult.h b/src/plugins/autotest/testresult.h index 0f193eb3558..ca4ed003a21 100644 --- a/src/plugins/autotest/testresult.h +++ b/src/plugins/autotest/testresult.h @@ -96,11 +96,13 @@ public: QString description() const { return m_description; } Utils::FilePath fileName() const { return m_file; } int line() const { return m_line; } + std::optional duration() const { return m_duration; } QVariant extraData() const { return m_hooks.extraData; } void setDescription(const QString &description) { m_description = description; } void setFileName(const Utils::FilePath &fileName) { m_file = fileName; } void setLine(int line) { m_line = line; } + void setDuration(const QString &milliSeconds) { m_duration.emplace(milliSeconds); } void setResult(ResultType type) { m_result = type; } static ResultType resultFromString(const QString &resultString); @@ -114,6 +116,7 @@ public: private: std::optional m_id = {}; + std::optional m_duration; QString m_name; ResultType m_result = ResultType::Invalid; // the real result.. QString m_description; diff --git a/src/plugins/autotest/testresultdelegate.cpp b/src/plugins/autotest/testresultdelegate.cpp index 8c9f11b2c79..f50a8dcf4a7 100644 --- a/src/plugins/autotest/testresultdelegate.cpp +++ b/src/plugins/autotest/testresultdelegate.cpp @@ -49,7 +49,7 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->fillRect(opt.rect, background); painter->setPen(foreground); - const LayoutPositions positions(opt, resultFilterModel); + const LayoutPositions positions(opt, resultFilterModel, m_showDuration); const TestResult testResult = resultFilterModel->testResult(index); QTC_ASSERT(testResult.isValid(), painter->restore(); return); @@ -90,6 +90,16 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op fm.elidedText(output.left(2000), Qt::ElideRight, positions.textAreaWidth())); } + if (testResult.result() == ResultType::TestStart && m_showDuration && testResult.duration()) { + const QString txt = testResult.duration().value() + " ms"; + QPen tmp = painter->pen(); + painter->setPen(opt.palette.mid().color()); + painter->setClipRect(positions.durationArea()); + option.widget->style()->drawItemText(painter, positions.durationArea(), Qt::AlignRight, + opt.palette, true, txt); + painter->setPen(tmp); + } + const QString file = testResult.fileName().fileName(); painter->setClipRect(positions.fileArea()); painter->drawText(positions.fileAreaLeft(), positions.top() + fm.ascent(), file); @@ -119,7 +129,7 @@ QSize TestResultDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo QFontMetrics fm(opt.font); int fontHeight = fm.height(); TestResultFilterModel *resultFilterModel = static_cast(view->model()); - LayoutPositions positions(opt, resultFilterModel); + LayoutPositions positions(opt, resultFilterModel, m_showDuration); const int depth = resultFilterModel->itemForIndex(index)->level() + 1; const int indentation = depth * view->style()->pixelMetric(QStyle::PM_TreeViewIndentation, &opt); diff --git a/src/plugins/autotest/testresultdelegate.h b/src/plugins/autotest/testresultdelegate.h index b5d988adb65..9b263835f32 100644 --- a/src/plugins/autotest/testresultdelegate.h +++ b/src/plugins/autotest/testresultdelegate.h @@ -21,6 +21,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void clearCache(); + void setShowDuration(bool showDuration) { m_showDuration = showDuration; } private: void limitTextOutput(QString &output) const; @@ -32,43 +33,57 @@ private: mutable QTextLayout m_lastCalculatedLayout; mutable int m_lastCalculatedHeight = 0; mutable int m_lastWidth = -1; + bool m_showDuration = true; class LayoutPositions { public: - LayoutPositions(QStyleOptionViewItem &options, const TestResultFilterModel *filterModel) + LayoutPositions(QStyleOptionViewItem &options, const TestResultFilterModel *filterModel, + bool showDuration) : m_top(options.rect.top()), m_left(options.rect.left()), - m_right(options.rect.right()) + m_right(options.rect.right()), + m_showDuration(showDuration) { 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).horizontalAdvance("XXXXXXXX"); + m_durationAreaWidth = QFontMetrics(options.font).horizontalAdvance("XXXXXXXX ms"); - int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; + int flexibleArea = (m_showDuration ? durationAreaLeft() : fileAreaLeft()) + - textAreaLeft() - ItemSpacing; if (m_maxFileLength > flexibleArea / 2) m_realFileLength = flexibleArea / 2; m_fontHeight = QFontMetrics(options.font).height(); } - int top() const { return m_top + ITEM_MARGIN; } - int left() const { return m_left + ITEM_MARGIN; } - int right() const { return m_right - ITEM_MARGIN; } - int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; } + int top() const { return m_top + ItemMargin; } + int left() const { return m_left + ItemMargin; } + int right() const { return m_right - ItemMargin; } + int minimumHeight() const { return IconSize + 2 * ItemMargin; } - int iconSize() const { return ICON_SIZE; } - int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; } - int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; } - int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); } - int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; } + int iconSize() const { return IconSize; } + int typeAreaLeft() const { return left() + IconSize + ItemSpacing; } + int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ItemSpacing; } + int textAreaWidth() const + { + if (m_showDuration) + return durationAreaLeft() - 3 * ItemSpacing - textAreaLeft(); + return fileAreaLeft() - ItemSpacing - textAreaLeft(); + } + int durationAreaLeft() const { return fileAreaLeft() - 3 * ItemSpacing - m_durationAreaWidth; } + int durationAreaWidth() const { return m_durationAreaWidth; } + int fileAreaLeft() const { return lineAreaLeft() - ItemSpacing - m_realFileLength; } int lineAreaLeft() const { return right() - m_maxLineLength; } QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), m_fontHeight); } + QRect durationArea() const { return QRect(durationAreaLeft(), top(), + durationAreaWidth(), m_fontHeight); } QRect fileArea() const { return QRect(fileAreaLeft(), top(), - m_realFileLength + ITEM_SPACING, m_fontHeight); } + m_realFileLength + ItemSpacing, m_fontHeight); } QRect lineArea() const { return QRect(lineAreaLeft(), top(), m_maxLineLength, m_fontHeight); } @@ -82,10 +97,12 @@ private: int m_right; int m_fontHeight; int m_typeAreaWidth; + int m_durationAreaWidth; + bool m_showDuration; - static const int ICON_SIZE = 16; - static const int ITEM_MARGIN = 2; - static const int ITEM_SPACING = 4; + static constexpr int IconSize = 16; + static constexpr int ItemMargin = 2; + static constexpr int ItemSpacing = 4; }; }; diff --git a/src/plugins/autotest/testresultmodel.cpp b/src/plugins/autotest/testresultmodel.cpp index deb7d99fec7..46c6d71b8f4 100644 --- a/src/plugins/autotest/testresultmodel.cpp +++ b/src/plugins/autotest/testresultmodel.cpp @@ -117,12 +117,16 @@ static bool isSignificant(ResultType type) } void TestResultItem::updateResult(bool &changed, ResultType addedChildType, - const std::optional &summary) + const std::optional &summary, + const std::optional duration) { changed = false; if (m_testResult.result() != ResultType::TestStart) return; + if (addedChildType == ResultType::TestEnd && duration) + m_testResult.setDuration(*duration); + if (!isSignificant(addedChildType) || (addedChildType == ResultType::TestStart && !summary)) return; @@ -244,7 +248,8 @@ void TestResultModel::updateParent(const TestResultItem *item) if (parentItem == rootItem()) // do not update invisible root item return; bool changed = false; - parentItem->updateResult(changed, item->testResult().result(), item->summaryResult()); + parentItem->updateResult(changed, item->testResult().result(), item->summaryResult(), + item->testResult().duration()); bool changedType = parentItem->updateDescendantTypes(item->testResult().result()); if (!changed && !changedType) return; diff --git a/src/plugins/autotest/testresultmodel.h b/src/plugins/autotest/testresultmodel.h index cbf3422b78f..add1f717538 100644 --- a/src/plugins/autotest/testresultmodel.h +++ b/src/plugins/autotest/testresultmodel.h @@ -37,7 +37,8 @@ public: }; void updateResult(bool &changed, ResultType addedChildType, - const std::optional &summary); + const std::optional &summary, + const std::optional duration); TestResultItem *intermediateFor(const TestResultItem *item) const; TestResultItem *createAndAddIntermediateFor(const TestResultItem *child); diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index 7631379a9af..642eb67cb6a 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -213,6 +213,22 @@ void TestResultsPane::createToolButtons() m_outputToggleButton->setToolTip(Tr::tr("Switch Between Visual and Text Display")); m_outputToggleButton->setEnabled(true); connect(m_outputToggleButton, &QToolButton::clicked, this, &TestResultsPane::toggleOutputStyle); + m_showDurationButton = new QToolButton(m_treeView); + auto icon = Utils::Icon({{":/utils/images/stopwatch.png", Utils::Theme::IconsBaseColor}}); + m_showDurationButton->setIcon(icon.icon()); + m_showDurationButton->setToolTip(Tr::tr("Show Durations")); + m_showDurationButton->setCheckable(true); + m_showDurationButton->setChecked(true); + connect(m_showDurationButton, &QToolButton::toggled, this, [this](bool checked) { + if (auto trd = qobject_cast(m_treeView->itemDelegate())) { + trd->setShowDuration(checked); + if (auto rowCount = m_model->rowCount()) { + QModelIndex tl = m_model->index(0, 0); + QModelIndex br = m_model->index(rowCount - 1, 0); + emit m_model->dataChanged(tl, br, {Qt::DisplayRole}); + } + } + }); } static TestResultsPane *s_instance = nullptr; @@ -272,7 +288,8 @@ QWidget *TestResultsPane::outputWidget(QWidget *parent) QList TestResultsPane::toolBarWidgets() const { QList result = {m_expandCollapse, m_runAll, m_runSelected, m_runFailed, - m_runFile, m_stopTestRun, m_outputToggleButton, m_filterButton}; + m_runFile, m_stopTestRun, m_showDurationButton, + m_outputToggleButton, m_filterButton}; for (QWidget *widget : IOutputPane::toolBarWidgets()) result.append(widget); return result; diff --git a/src/plugins/autotest/testresultspane.h b/src/plugins/autotest/testresultspane.h index 811f749b48f..b8785d25eb2 100644 --- a/src/plugins/autotest/testresultspane.h +++ b/src/plugins/autotest/testresultspane.h @@ -115,6 +115,7 @@ private: QToolButton *m_stopTestRun; QToolButton *m_filterButton; QToolButton *m_outputToggleButton; + QToolButton *m_showDurationButton; Core::OutputWindow *m_textOutput; QMenu *m_filterMenu; bool m_autoScroll = false;