2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2015-01-16 13:44:24 +01:00
|
|
|
|
2015-12-09 09:46:33 +01:00
|
|
|
#include "testoutputreader.h"
|
2020-10-09 13:07:55 +02:00
|
|
|
|
2022-07-13 18:31:56 +02:00
|
|
|
#include "autotesttr.h"
|
2020-07-27 17:52:59 +02:00
|
|
|
#include "testtreeitem.h"
|
2015-01-16 13:44:24 +01:00
|
|
|
|
2018-11-05 15:58:27 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2022-06-10 10:18:34 +02:00
|
|
|
#include <utils/qtcprocess.h>
|
2018-11-05 15:58:27 +01:00
|
|
|
|
TestRunner: Reuse TaskTree
Get rid of QFutureInterface argument from
ITestConfiguration::createOutputReader() and from
TestOutputReader c'tor.
The fine-grained progress reporting was broken anyway:
1. The assumption was that testCaseCount was meant to be
the total number of test functions executed. It didn't
include the initTestCase() and cleanupTestCase(),
while those were reported on runtime apparently
(and exceeding the max progress by 2).
2. In case of tst_qtcprocess, when the whole test was run,
the testCaseCount reported 41, while the real
number of functions was 26 (+2 = 28 for init/cleanup).
3. While the max progress was set to testCaseCount initially,
the corresponding FutureProgress rendered the progress
always in 0-100 range, what didn't match the reality.
Instead, rely on TaskTree progress, which resolution
is per test as a whole. So, when executing a series
of tests this should scale fine. In addition, the
progress advances fluently according to the expected
run time - with 10 seconds hardcoded.
The original code locations, where progress was bumped,
are left with a TODO comment for any possible future tweaks.
Like in case of result reporting, fine-grained progress
reporting may be implemented by providing additional signal,
so there is no need for QFutureInterface inside
TestOutputReader.
Change-Id: Idc11d55e3a49dac8d1788948b9a82f68199203c6
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2023-01-17 00:45:50 +01:00
|
|
|
#include <QRegularExpression>
|
2015-01-16 13:44:24 +01:00
|
|
|
|
2023-01-16 15:00:15 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2015-01-16 13:44:24 +01:00
|
|
|
namespace Autotest {
|
|
|
|
|
|
2023-01-16 15:00:15 +01:00
|
|
|
FilePath TestOutputReader::constructSourceFilePath(const FilePath &path, const QString &file)
|
2021-05-26 15:50:03 +02:00
|
|
|
{
|
2023-01-16 15:00:15 +01:00
|
|
|
const FilePath filePath = path.resolvePath(file);
|
|
|
|
|
return filePath.isReadableFile() ? filePath : FilePath();
|
2021-05-26 15:50:03 +02:00
|
|
|
}
|
|
|
|
|
|
TestRunner: Reuse TaskTree
Get rid of QFutureInterface argument from
ITestConfiguration::createOutputReader() and from
TestOutputReader c'tor.
The fine-grained progress reporting was broken anyway:
1. The assumption was that testCaseCount was meant to be
the total number of test functions executed. It didn't
include the initTestCase() and cleanupTestCase(),
while those were reported on runtime apparently
(and exceeding the max progress by 2).
2. In case of tst_qtcprocess, when the whole test was run,
the testCaseCount reported 41, while the real
number of functions was 26 (+2 = 28 for init/cleanup).
3. While the max progress was set to testCaseCount initially,
the corresponding FutureProgress rendered the progress
always in 0-100 range, what didn't match the reality.
Instead, rely on TaskTree progress, which resolution
is per test as a whole. So, when executing a series
of tests this should scale fine. In addition, the
progress advances fluently according to the expected
run time - with 10 seconds hardcoded.
The original code locations, where progress was bumped,
are left with a TODO comment for any possible future tweaks.
Like in case of result reporting, fine-grained progress
reporting may be implemented by providing additional signal,
so there is no need for QFutureInterface inside
TestOutputReader.
Change-Id: Idc11d55e3a49dac8d1788948b9a82f68199203c6
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2023-01-17 00:45:50 +01:00
|
|
|
TestOutputReader::TestOutputReader(QtcProcess *testApplication, const FilePath &buildDirectory)
|
|
|
|
|
: m_buildDir(buildDirectory)
|
2015-01-16 13:44:24 +01:00
|
|
|
{
|
2019-11-11 08:03:16 +01:00
|
|
|
auto chopLineBreak = [](QByteArray line) {
|
|
|
|
|
if (line.endsWith('\n'))
|
|
|
|
|
line.chop(1);
|
|
|
|
|
if (line.endsWith('\r'))
|
|
|
|
|
line.chop(1);
|
|
|
|
|
return line;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-16 22:10:10 +01:00
|
|
|
if (testApplication) {
|
TestRunner: Reuse TaskTree
Get rid of QFutureInterface argument from
ITestConfiguration::createOutputReader() and from
TestOutputReader c'tor.
The fine-grained progress reporting was broken anyway:
1. The assumption was that testCaseCount was meant to be
the total number of test functions executed. It didn't
include the initTestCase() and cleanupTestCase(),
while those were reported on runtime apparently
(and exceeding the max progress by 2).
2. In case of tst_qtcprocess, when the whole test was run,
the testCaseCount reported 41, while the real
number of functions was 26 (+2 = 28 for init/cleanup).
3. While the max progress was set to testCaseCount initially,
the corresponding FutureProgress rendered the progress
always in 0-100 range, what didn't match the reality.
Instead, rely on TaskTree progress, which resolution
is per test as a whole. So, when executing a series
of tests this should scale fine. In addition, the
progress advances fluently according to the expected
run time - with 10 seconds hardcoded.
The original code locations, where progress was bumped,
are left with a TODO comment for any possible future tweaks.
Like in case of result reporting, fine-grained progress
reporting may be implemented by providing additional signal,
so there is no need for QFutureInterface inside
TestOutputReader.
Change-Id: Idc11d55e3a49dac8d1788948b9a82f68199203c6
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2023-01-17 00:45:50 +01:00
|
|
|
connect(testApplication, &QtcProcess::started, this, [this, testApplication] {
|
|
|
|
|
m_id = testApplication->commandLine().executable().toUserOutput();
|
|
|
|
|
});
|
2023-01-16 22:10:10 +01:00
|
|
|
testApplication->setStdOutLineCallback([this, &chopLineBreak](const QString &line) {
|
2022-06-10 10:18:34 +02:00
|
|
|
processStdOutput(chopLineBreak(line.toUtf8()));
|
2016-07-21 16:02:42 +02:00
|
|
|
});
|
2023-01-16 22:10:10 +01:00
|
|
|
testApplication->setStdErrLineCallback([this, &chopLineBreak](const QString &line) {
|
2022-06-10 10:18:34 +02:00
|
|
|
processStdError(chopLineBreak(line.toUtf8()));
|
2016-07-21 16:02:42 +02:00
|
|
|
});
|
|
|
|
|
}
|
2016-04-18 10:24:54 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-27 17:52:59 +02:00
|
|
|
TestOutputReader::~TestOutputReader()
|
|
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
if (m_sanitizerResult.isValid())
|
2020-07-27 17:52:59 +02:00
|
|
|
sendAndResetSanitizerResult();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-11 08:03:16 +01:00
|
|
|
void TestOutputReader::processStdOutput(const QByteArray &outputLine)
|
2018-11-06 16:00:48 +01:00
|
|
|
{
|
2019-11-11 08:03:16 +01:00
|
|
|
processOutputLine(outputLine);
|
2019-11-06 14:25:16 +01:00
|
|
|
emit newOutputLineAvailable(outputLine, OutputChannel::StdOut);
|
2018-11-06 16:00:48 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-11 08:03:16 +01:00
|
|
|
void TestOutputReader::processStdError(const QByteArray &outputLine)
|
2016-04-18 10:24:54 +02:00
|
|
|
{
|
2020-07-27 17:52:59 +02:00
|
|
|
checkForSanitizerOutput(outputLine);
|
2019-11-06 14:25:16 +01:00
|
|
|
emit newOutputLineAvailable(outputLine, OutputChannel::StdErr);
|
2015-01-16 13:44:24 +01:00
|
|
|
}
|
|
|
|
|
|
2018-01-16 08:41:53 +01:00
|
|
|
void TestOutputReader::reportCrash()
|
|
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult result = createDefaultResult();
|
|
|
|
|
result.setDescription(Tr::tr("Test executable crashed."));
|
|
|
|
|
result.setResult(ResultType::MessageFatal);
|
2023-01-16 15:47:36 +01:00
|
|
|
emit newResult(result);
|
2018-01-16 08:41:53 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 14:11:19 +01:00
|
|
|
void TestOutputReader::createAndReportResult(const QString &message, ResultType type)
|
2018-04-16 09:45:01 +02:00
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult result = createDefaultResult();
|
|
|
|
|
result.setDescription(message);
|
|
|
|
|
result.setResult(type);
|
2018-04-16 09:45:01 +02:00
|
|
|
reportResult(result);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-06 14:26:40 +01:00
|
|
|
void TestOutputReader::resetCommandlineColor()
|
|
|
|
|
{
|
|
|
|
|
emit newOutputLineAvailable("\u001B[m", OutputChannel::StdOut);
|
|
|
|
|
emit newOutputLineAvailable("\u001B[m", OutputChannel::StdErr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString TestOutputReader::removeCommandlineColors(const QString &original)
|
|
|
|
|
{
|
|
|
|
|
static const QRegularExpression pattern("\u001B\\[.*?m");
|
|
|
|
|
QString result = original;
|
|
|
|
|
while (!result.isEmpty()) {
|
|
|
|
|
QRegularExpressionMatch match = pattern.match(result);
|
|
|
|
|
if (match.hasMatch())
|
|
|
|
|
result.remove(match.capturedStart(), match.captured().length());
|
|
|
|
|
else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 16:25:51 +01:00
|
|
|
void TestOutputReader::reportResult(const TestResult &result)
|
2017-09-20 11:20:25 +02:00
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
if (m_sanitizerResult.isValid())
|
2021-02-18 08:32:27 +01:00
|
|
|
sendAndResetSanitizerResult();
|
2023-01-16 15:47:36 +01:00
|
|
|
emit newResult(result);
|
2017-09-20 11:20:25 +02:00
|
|
|
m_hadValidOutput = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-27 17:52:59 +02:00
|
|
|
void TestOutputReader::checkForSanitizerOutput(const QByteArray &line)
|
|
|
|
|
{
|
|
|
|
|
const QString lineStr = removeCommandlineColors(QString::fromUtf8(line));
|
|
|
|
|
if (m_sanitizerOutputMode == SanitizerOutputMode::Asan) {
|
|
|
|
|
// append the new line and check for end
|
|
|
|
|
m_sanitizerLines.append(lineStr);
|
2020-07-28 07:35:20 +02:00
|
|
|
static const QRegularExpression regex("^==\\d+==\\s*ABORTING.*");
|
2020-07-27 17:52:59 +02:00
|
|
|
if (regex.match(lineStr).hasMatch())
|
|
|
|
|
sendAndResetSanitizerResult();
|
2020-07-28 07:35:20 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const QRegularExpression regex("^==\\d+==\\s*(ERROR|WARNING|Sanitizer CHECK failed):.*");
|
|
|
|
|
static const QRegularExpression ubsanRegex("^(.*):(\\d+):(\\d+): runtime error:.*");
|
|
|
|
|
QRegularExpressionMatch match = regex.match(lineStr);
|
|
|
|
|
SanitizerOutputMode mode = SanitizerOutputMode::None;
|
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
|
mode = SanitizerOutputMode::Asan;
|
2020-07-27 17:52:59 +02:00
|
|
|
} else {
|
2020-07-28 07:35:20 +02:00
|
|
|
match = ubsanRegex.match(lineStr);
|
|
|
|
|
if (m_sanitizerOutputMode == SanitizerOutputMode::Ubsan && !match.hasMatch()) {
|
2020-07-27 17:52:59 +02:00
|
|
|
m_sanitizerLines.append(lineStr);
|
2020-07-28 07:35:20 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (match.hasMatch())
|
|
|
|
|
mode = SanitizerOutputMode::Ubsan;
|
|
|
|
|
}
|
|
|
|
|
if (mode != SanitizerOutputMode::None) {
|
2023-01-14 16:25:51 +01:00
|
|
|
if (m_sanitizerResult.isValid()) // we have a result that has not been reported yet
|
2020-07-28 07:35:20 +02:00
|
|
|
sendAndResetSanitizerResult();
|
|
|
|
|
|
|
|
|
|
m_sanitizerOutputMode = mode;
|
|
|
|
|
m_sanitizerResult = createDefaultResult();
|
|
|
|
|
m_sanitizerLines.append("Sanitizer Issue");
|
|
|
|
|
m_sanitizerLines.append(lineStr);
|
|
|
|
|
if (m_sanitizerOutputMode == SanitizerOutputMode::Ubsan) {
|
2023-01-16 15:00:15 +01:00
|
|
|
const FilePath path = constructSourceFilePath(m_buildDir, match.captured(1));
|
2020-07-28 07:35:20 +02:00
|
|
|
// path may be empty if not existing - so, provide at least what we have
|
2023-01-14 16:25:51 +01:00
|
|
|
m_sanitizerResult.setFileName(
|
2023-01-16 15:00:15 +01:00
|
|
|
path.exists() ? path : FilePath::fromString(match.captured(1)));
|
2023-01-14 16:25:51 +01:00
|
|
|
m_sanitizerResult.setLine(match.captured(2).toInt());
|
2020-07-27 17:52:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestOutputReader::sendAndResetSanitizerResult()
|
|
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
QTC_ASSERT(m_sanitizerResult.isValid(), return);
|
|
|
|
|
m_sanitizerResult.setDescription(m_sanitizerLines.join('\n'));
|
|
|
|
|
m_sanitizerResult.setResult(m_sanitizerOutputMode == SanitizerOutputMode::Ubsan
|
|
|
|
|
? ResultType::Fail : ResultType::MessageFatal);
|
2020-07-27 17:52:59 +02:00
|
|
|
|
2023-01-14 16:25:51 +01:00
|
|
|
if (m_sanitizerResult.fileName().isEmpty()) {
|
|
|
|
|
const ITestTreeItem *testItem = m_sanitizerResult.findTestTreeItem();
|
2020-07-27 17:52:59 +02:00
|
|
|
if (testItem && testItem->line()) {
|
2023-01-14 16:25:51 +01:00
|
|
|
m_sanitizerResult.setFileName(testItem->filePath());
|
|
|
|
|
m_sanitizerResult.setLine(testItem->line());
|
2020-07-27 17:52:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 15:47:36 +01:00
|
|
|
emit newResult(m_sanitizerResult);
|
2021-02-18 08:32:27 +01:00
|
|
|
m_hadValidOutput = true;
|
2020-07-27 17:52:59 +02:00
|
|
|
m_sanitizerLines.clear();
|
2023-01-14 16:25:51 +01:00
|
|
|
m_sanitizerResult = {};
|
2020-07-27 17:52:59 +02:00
|
|
|
m_sanitizerOutputMode = SanitizerOutputMode::None;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-16 13:44:24 +01:00
|
|
|
} // namespace Autotest
|