From 5a95becf2dba4850c85ff2dbb5c72a0177f466a7 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 12 Jul 2024 15:42:09 +0200 Subject: [PATCH] AutoTest: Display results duration if possible Some test frameworks provide duration information and we gather and display them rather hidden. Display duration of the test execution more prominent if it is available and the user has not turned off feature. Task-number: QTCREATORBUG-31242 Change-Id: I27641a5f811299e5876cce9e4739078fe3c2ae19 Reviewed-by: David Schulz --- .../autotest/gtest/gtestoutputreader.cpp | 9 ++-- .../autotest/qtest/qttestoutputreader.cpp | 3 ++ src/plugins/autotest/testresult.h | 3 ++ src/plugins/autotest/testresultdelegate.cpp | 14 +++++- src/plugins/autotest/testresultdelegate.h | 49 +++++++++++++------ src/plugins/autotest/testresultmodel.cpp | 9 +++- src/plugins/autotest/testresultmodel.h | 3 +- src/plugins/autotest/testresultspane.cpp | 19 ++++++- src/plugins/autotest/testresultspane.h | 1 + 9 files changed, 85 insertions(+), 25 deletions(-) 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;