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 <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2024-07-12 15:42:09 +02:00
parent 2c413758a6
commit 5a95becf2d
9 changed files with 85 additions and 25 deletions

View File

@@ -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();

View File

@@ -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."));

View File

@@ -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<QString> 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<QString> m_id = {};
std::optional<QString> m_duration;
QString m_name;
ResultType m_result = ResultType::Invalid; // the real result..
QString m_description;

View File

@@ -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<TestResultFilterModel *>(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);

View File

@@ -21,6 +21,7 @@ public:
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void currentChanged(const QModelIndex &current, 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<TestResultModel *>(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;
};
};

View File

@@ -117,12 +117,16 @@ static bool isSignificant(ResultType type)
}
void TestResultItem::updateResult(bool &changed, ResultType addedChildType,
const std::optional<SummaryEvaluation> &summary)
const std::optional<SummaryEvaluation> &summary,
const std::optional<QString> 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;

View File

@@ -37,7 +37,8 @@ public:
};
void updateResult(bool &changed, ResultType addedChildType,
const std::optional<SummaryEvaluation> &summary);
const std::optional<SummaryEvaluation> &summary,
const std::optional<QString> duration);
TestResultItem *intermediateFor(const TestResultItem *item) const;
TestResultItem *createAndAddIntermediateFor(const TestResultItem *child);

View File

@@ -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<TestResultDelegate *>(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<QWidget *> TestResultsPane::toolBarWidgets() const
{
QList<QWidget *> 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;

View File

@@ -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;