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
|
2016-05-11 13:02:42 +02:00
|
|
|
|
|
|
|
|
#include "gtestoutputreader.h"
|
2022-07-13 18:31:56 +02:00
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
#include "gtestresult.h"
|
2017-08-04 18:33:50 +02:00
|
|
|
#include "../testtreeitem.h"
|
2022-07-13 18:31:56 +02:00
|
|
|
#include "../autotesttr.h"
|
|
|
|
|
|
2019-01-08 16:05:57 +01:00
|
|
|
#include <utils/hostosinfo.h>
|
2024-02-27 16:08:45 +01:00
|
|
|
#include <utils/qtcprocess.h>
|
2016-05-11 13:02:42 +02:00
|
|
|
|
2019-05-24 10:12:43 +02:00
|
|
|
#include <QRegularExpression>
|
2016-05-11 13:02:42 +02:00
|
|
|
|
2023-01-16 15:00:15 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
namespace Autotest {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
GTestOutputReader::GTestOutputReader(Process *testApplication,
|
2023-01-16 15:00:15 +01:00
|
|
|
const FilePath &buildDirectory,
|
|
|
|
|
const FilePath &projectFile)
|
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(testApplication, buildDirectory)
|
2017-08-04 18:33:50 +02:00
|
|
|
, m_projectFile(projectFile)
|
2016-05-11 13:02:42 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-11 08:03:16 +01:00
|
|
|
void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
|
2016-05-11 13:02:42 +02:00
|
|
|
{
|
2019-05-24 10:12:43 +02:00
|
|
|
static const QRegularExpression newTestStarts("^\\[-{10}\\] \\d+ tests? from (.*)$");
|
|
|
|
|
static const QRegularExpression testEnds("^\\[-{10}\\] \\d+ tests? from (.*) \\((.*)\\)$");
|
|
|
|
|
static const QRegularExpression newTestSetStarts("^\\[ RUN \\] (.*)$");
|
|
|
|
|
static const QRegularExpression testSetSuccess("^\\[ OK \\] (.*) \\((.*)\\)$");
|
|
|
|
|
static const QRegularExpression testSetFail("^\\[ FAILED \\] (.*) \\((\\d+ ms)\\)$");
|
2021-06-21 09:36:15 +02:00
|
|
|
static const QRegularExpression testDeath("^\\[ DEATH \\] (.*)$");
|
2020-03-20 11:48:37 +01:00
|
|
|
static const QRegularExpression testSetSkipped("^\\[ SKIPPED \\] (.*) \\((\\d+ ms)\\)$");
|
2019-05-24 10:12:43 +02:00
|
|
|
static const QRegularExpression disabledTests("^ YOU HAVE (\\d+) DISABLED TESTS?$");
|
|
|
|
|
static const QRegularExpression iterations("^Repeating all tests "
|
|
|
|
|
"\\(iteration (\\d+)\\) \\. \\. \\.$");
|
2019-12-11 11:29:15 +01:00
|
|
|
static const QRegularExpression logging("^\\[( FATAL | ERROR |WARNING| INFO )\\] "
|
|
|
|
|
"(.*):(\\d+):: (.*)$");
|
2016-05-11 13:02:42 +02:00
|
|
|
|
2019-11-06 14:26:40 +01:00
|
|
|
const QString line = removeCommandlineColors(QString::fromLatin1(outputLine));
|
2016-07-21 10:08:11 +02:00
|
|
|
if (line.trimmed().isEmpty())
|
|
|
|
|
return;
|
2016-05-11 13:02:42 +02:00
|
|
|
|
2019-05-24 10:12:43 +02:00
|
|
|
struct ExactMatch : public QRegularExpressionMatch
|
|
|
|
|
{
|
|
|
|
|
ExactMatch(const QRegularExpressionMatch &other) : QRegularExpressionMatch(other) {}
|
|
|
|
|
operator bool() const { return hasMatch(); }
|
|
|
|
|
};
|
|
|
|
|
|
2016-09-29 12:15:43 +02:00
|
|
|
if (!line.startsWith('[')) {
|
|
|
|
|
m_description.append(line).append('\n');
|
2019-05-24 10:12:43 +02:00
|
|
|
if (ExactMatch match = iterations.match(line)) {
|
|
|
|
|
m_iteration = match.captured(1).toInt();
|
2016-07-21 10:08:11 +02:00
|
|
|
m_description.clear();
|
|
|
|
|
} else if (line.startsWith(QStringLiteral("Note:"))) {
|
2023-02-20 15:45:46 +01:00
|
|
|
// notes contain insignificant information we fail to include properly into the
|
|
|
|
|
// visual tree, so ignore them here as they are available inside the text display anyhow
|
2016-05-11 13:02:42 +02:00
|
|
|
m_description.clear();
|
2019-05-24 10:12:43 +02:00
|
|
|
} else if (ExactMatch match = disabledTests.match(line)) {
|
|
|
|
|
m_disabled = match.captured(1).toInt();
|
2016-05-11 13:02:42 +02:00
|
|
|
m_description.clear();
|
2016-07-21 10:08:11 +02:00
|
|
|
}
|
2016-12-14 13:30:33 +01:00
|
|
|
return;
|
2016-07-21 10:08:11 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-24 10:12:43 +02:00
|
|
|
if (ExactMatch match = testEnds.match(line)) {
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(ResultType::TestEnd);
|
2023-06-20 08:00:49 +02:00
|
|
|
testResult.setDescription(Tr::tr("Test execution took %1.").arg(match.captured(2)));
|
2018-01-16 08:41:18 +01:00
|
|
|
reportResult(testResult);
|
2019-05-24 07:17:50 +02:00
|
|
|
m_currentTestSuite.clear();
|
|
|
|
|
m_currentTestCase.clear();
|
2019-05-24 10:12:43 +02:00
|
|
|
} else if (ExactMatch match = newTestStarts.match(line)) {
|
|
|
|
|
setCurrentTestSuite(match.captured(1));
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(ResultType::TestStart);
|
2016-07-21 10:08:11 +02:00
|
|
|
if (m_iteration > 1) {
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setDescription(Tr::tr("Repeating test suite %1 (iteration %2)")
|
2019-05-24 07:17:50 +02:00
|
|
|
.arg(m_currentTestSuite).arg(m_iteration));
|
2016-07-21 10:08:11 +02:00
|
|
|
} else {
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setDescription(Tr::tr("Executing test suite %1").arg(m_currentTestSuite));
|
2016-07-21 10:08:11 +02:00
|
|
|
}
|
2017-09-20 11:20:25 +02:00
|
|
|
reportResult(testResult);
|
2019-05-24 10:12:43 +02:00
|
|
|
} else if (ExactMatch match = newTestSetStarts.match(line)) {
|
2020-03-20 11:48:37 +01:00
|
|
|
m_testSetStarted = true;
|
2019-05-24 10:12:43 +02:00
|
|
|
setCurrentTestCase(match.captured(1));
|
2023-02-15 16:25:16 +01:00
|
|
|
GTestResult testResult({}, {}, m_projectFile);
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setResult(ResultType::MessageCurrentTest);
|
|
|
|
|
testResult.setDescription(Tr::tr("Entering test case %1").arg(m_currentTestCase));
|
2017-09-20 11:20:25 +02:00
|
|
|
reportResult(testResult);
|
2016-07-21 10:08:11 +02:00
|
|
|
m_description.clear();
|
2019-05-24 10:12:43 +02:00
|
|
|
} else if (ExactMatch match = testSetSuccess.match(line)) {
|
2020-03-20 11:48:37 +01:00
|
|
|
m_testSetStarted = false;
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(ResultType::Pass);
|
|
|
|
|
testResult.setDescription(m_description);
|
2018-01-16 08:41:18 +01:00
|
|
|
reportResult(testResult);
|
2016-07-21 10:08:11 +02:00
|
|
|
m_description.clear();
|
2016-12-14 13:30:33 +01:00
|
|
|
testResult = createDefaultResult();
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setResult(ResultType::MessageInternal);
|
|
|
|
|
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
|
2018-01-16 08:41:18 +01:00
|
|
|
reportResult(testResult);
|
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
|
|
|
// TODO: bump progress?
|
2019-05-24 10:12:43 +02:00
|
|
|
} else if (ExactMatch match = testSetFail.match(line)) {
|
2020-03-20 11:48:37 +01:00
|
|
|
m_testSetStarted = false;
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(ResultType::Fail);
|
2016-07-21 10:08:11 +02:00
|
|
|
m_description.chop(1);
|
2020-03-20 11:48:37 +01:00
|
|
|
handleDescriptionAndReportResult(testResult);
|
2016-12-14 13:30:33 +01:00
|
|
|
testResult = createDefaultResult();
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setResult(ResultType::MessageInternal);
|
|
|
|
|
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
|
2018-01-16 08:41:18 +01:00
|
|
|
reportResult(testResult);
|
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
|
|
|
// TODO: bump progress?
|
2020-03-20 11:48:37 +01:00
|
|
|
} else if (ExactMatch match = testSetSkipped.match(line)) {
|
|
|
|
|
if (!m_testSetStarted) // ignore SKIPPED at summary
|
|
|
|
|
return;
|
|
|
|
|
m_testSetStarted = false;
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(ResultType::Skip);
|
2020-03-20 11:48:37 +01:00
|
|
|
m_description.chop(1);
|
|
|
|
|
m_description.prepend(match.captured(1) + '\n');
|
|
|
|
|
handleDescriptionAndReportResult(testResult);
|
|
|
|
|
testResult = createDefaultResult();
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setResult(ResultType::MessageInternal);
|
|
|
|
|
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
|
2020-03-20 11:48:37 +01:00
|
|
|
reportResult(testResult);
|
2019-12-11 11:29:15 +01:00
|
|
|
} else if (ExactMatch match = logging.match(line)) {
|
|
|
|
|
const QString severity = match.captured(1).trimmed();
|
|
|
|
|
ResultType type = ResultType::Invalid;
|
|
|
|
|
switch (severity.at(0).toLatin1()) {
|
|
|
|
|
case 'I': type = ResultType::MessageInfo; break; // INFO
|
|
|
|
|
case 'W': type = ResultType::MessageWarn; break; // WARNING
|
|
|
|
|
case 'E': type = ResultType::MessageError; break; // ERROR
|
|
|
|
|
case 'F': type = ResultType::MessageFatal; break; // FATAL
|
|
|
|
|
}
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult testResult = createDefaultResult();
|
|
|
|
|
testResult.setResult(type);
|
|
|
|
|
testResult.setLine(match.captured(3).toInt());
|
2023-01-16 15:00:15 +01:00
|
|
|
const FilePath file = constructSourceFilePath(m_buildDir, match.captured(2));
|
2021-07-08 13:23:44 +02:00
|
|
|
if (file.exists())
|
2023-01-14 16:25:51 +01:00
|
|
|
testResult.setFileName(file);
|
|
|
|
|
testResult.setDescription(match.captured(4));
|
2019-12-11 11:29:15 +01:00
|
|
|
reportResult(testResult);
|
2021-06-21 09:36:15 +02:00
|
|
|
} else if (ExactMatch match = testDeath.match(line)) {
|
|
|
|
|
m_description.append(line);
|
|
|
|
|
m_description.append('\n');
|
2016-05-11 13:02:42 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-11 11:29:15 +01:00
|
|
|
void GTestOutputReader::processStdError(const QByteArray &outputLine)
|
|
|
|
|
{
|
|
|
|
|
// we need to process the output, GTest may uses both out streams
|
2020-07-27 17:52:59 +02:00
|
|
|
checkForSanitizerOutput(outputLine);
|
2019-12-11 11:29:15 +01:00
|
|
|
processOutputLine(outputLine);
|
|
|
|
|
emit newOutputLineAvailable(outputLine, OutputChannel::StdErr);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult GTestOutputReader::createDefaultResult() const
|
2016-12-14 13:30:33 +01:00
|
|
|
{
|
2023-01-14 16:25:51 +01:00
|
|
|
GTestResult result(id(), m_currentTestSuite, m_projectFile, m_currentTestCase, m_iteration);
|
|
|
|
|
const ITestTreeItem *testItem = result.findTestTreeItem();
|
2017-08-04 18:33:50 +02:00
|
|
|
if (testItem && testItem->line()) {
|
2023-01-14 16:25:51 +01:00
|
|
|
result.setFileName(testItem->filePath());
|
|
|
|
|
result.setLine(testItem->line());
|
2017-08-04 18:33:50 +02:00
|
|
|
}
|
2023-01-14 16:25:51 +01:00
|
|
|
return result;
|
2018-01-16 08:41:18 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-20 13:54:30 +02:00
|
|
|
void GTestOutputReader::onDone(int exitCode)
|
|
|
|
|
{
|
|
|
|
|
if (exitCode == 1 && !m_description.isEmpty()) {
|
|
|
|
|
createAndReportResult(Tr::tr("Running tests failed.\n %1\nExecutable: %2")
|
|
|
|
|
.arg(m_description).arg(id()), ResultType::MessageFatal);
|
|
|
|
|
}
|
|
|
|
|
// on Windows abort() will result in normal termination, but exit code will be set to 3
|
|
|
|
|
if (HostOsInfo::isWindowsHost() && exitCode == 3)
|
|
|
|
|
reportCrash();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-24 07:17:50 +02:00
|
|
|
void GTestOutputReader::setCurrentTestCase(const QString &testCase)
|
2018-01-16 08:41:18 +01:00
|
|
|
{
|
2019-05-24 07:17:50 +02:00
|
|
|
m_currentTestCase = testCase;
|
2018-01-16 08:41:18 +01:00
|
|
|
}
|
|
|
|
|
|
2019-05-24 07:17:50 +02:00
|
|
|
void GTestOutputReader::setCurrentTestSuite(const QString &testSuite)
|
2018-01-16 08:41:18 +01:00
|
|
|
{
|
2019-05-24 07:17:50 +02:00
|
|
|
m_currentTestSuite = testSuite;
|
2016-12-14 13:30:33 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-14 16:25:51 +01:00
|
|
|
void GTestOutputReader::handleDescriptionAndReportResult(const TestResult &testResult)
|
2020-03-20 11:48:37 +01:00
|
|
|
{
|
|
|
|
|
static const QRegularExpression failureLocation("^(.*):(\\d+): Failure$");
|
|
|
|
|
static const QRegularExpression skipOrErrorLocation("^(.*)\\((\\d+)\\): (Skipped|error:.*)$");
|
|
|
|
|
|
|
|
|
|
QStringList resultDescription;
|
2023-01-14 16:25:51 +01:00
|
|
|
TestResult result = testResult;
|
2020-03-20 11:48:37 +01:00
|
|
|
for (const QString &output : m_description.split('\n')) {
|
|
|
|
|
QRegularExpressionMatch innerMatch = failureLocation.match(output);
|
|
|
|
|
if (!innerMatch.hasMatch()) {
|
|
|
|
|
innerMatch = skipOrErrorLocation.match(output);
|
|
|
|
|
if (!innerMatch.hasMatch()) {
|
|
|
|
|
resultDescription << output;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-14 16:25:51 +01:00
|
|
|
result.setDescription(resultDescription.join('\n'));
|
2023-05-12 08:16:11 +02:00
|
|
|
reportResult(result);
|
2020-03-20 11:48:37 +01:00
|
|
|
resultDescription.clear();
|
|
|
|
|
|
2023-01-14 16:25:51 +01:00
|
|
|
result = createDefaultResult();
|
|
|
|
|
result.setResult(ResultType::MessageLocation);
|
|
|
|
|
result.setLine(innerMatch.captured(2).toInt());
|
2023-01-16 15:00:15 +01:00
|
|
|
const FilePath file = constructSourceFilePath(m_buildDir, innerMatch.captured(1));
|
2021-07-08 13:23:44 +02:00
|
|
|
if (file.exists())
|
2023-01-14 16:25:51 +01:00
|
|
|
result.setFileName(file);
|
2020-03-20 11:48:37 +01:00
|
|
|
resultDescription << output;
|
|
|
|
|
}
|
2023-01-14 16:25:51 +01:00
|
|
|
result.setDescription(resultDescription.join('\n'));
|
|
|
|
|
reportResult(result);
|
2020-03-20 11:48:37 +01:00
|
|
|
m_description.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-11 13:02:42 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Autotest
|