CustomParser: Add warning parser and output channel filter

There have been several requests on the mailing list or the
bug tracker to support parsing warnings for alien compilers
(sometimes slightly modified GCC).

Instead of natively supporting every compiler, users of less
frequently used compilers should use the custom parser to
parse errors and warnings.

The output channel filter for error and warning parser allows
to scan standard output, standard error or both channels.

Also added tests for twisted capture positions.

Task-number: QTCREATORBUG-11003
Change-Id: I5a5bd6f88cf21cde1c74962225067d4543693678
Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com>
This commit is contained in:
Andre Hartmann
2015-12-16 21:04:45 +01:00
committed by André Hartmann
parent 5a7d3e78db
commit 61a87799a3
7 changed files with 984 additions and 363 deletions

View File

@@ -40,20 +40,83 @@
using namespace Utils;
using namespace ProjectExplorer;
CustomParserSettings::CustomParserSettings() :
fileNameCap(1),
lineNumberCap(2),
messageCap(3)
{ }
bool CustomParserExpression::operator ==(const CustomParserExpression &other) const
{
return pattern() == other.pattern() && fileNameCap() == other.fileNameCap()
&& lineNumberCap() == other.lineNumberCap() && messageCap() == other.messageCap()
&& channel() == other.channel() && example() == other.example();
}
QString CustomParserExpression::pattern() const
{
return m_regExp.pattern();
}
void ProjectExplorer::CustomParserExpression::setPattern(const QString &pattern)
{
m_regExp.setPattern(pattern);
QTC_CHECK(m_regExp.isValid());
}
CustomParserExpression::CustomParserChannel CustomParserExpression::channel() const
{
return m_channel;
}
void CustomParserExpression::setChannel(CustomParserExpression::CustomParserChannel channel)
{
QTC_ASSERT(channel > ParseNoChannel && channel <= ParseBothChannels,
channel = ParseBothChannels);
m_channel = channel;
}
QString CustomParserExpression::example() const
{
return m_example;
}
void CustomParserExpression::setExample(const QString &example)
{
m_example = example;
}
int CustomParserExpression::messageCap() const
{
return m_messageCap;
}
void CustomParserExpression::setMessageCap(int messageCap)
{
m_messageCap = messageCap;
}
int CustomParserExpression::lineNumberCap() const
{
return m_lineNumberCap;
}
void CustomParserExpression::setLineNumberCap(int lineNumberCap)
{
m_lineNumberCap = lineNumberCap;
}
int CustomParserExpression::fileNameCap() const
{
return m_fileNameCap;
}
void CustomParserExpression::setFileNameCap(int fileNameCap)
{
m_fileNameCap = fileNameCap;
}
bool CustomParserSettings::operator ==(const CustomParserSettings &other) const
{
return errorPattern == other.errorPattern && fileNameCap == other.fileNameCap
&& lineNumberCap == other.lineNumberCap && messageCap == other.messageCap;
return error == other.error && warning == other.warning;
}
CustomParser::CustomParser(const CustomParserSettings &settings) :
m_parserChannels(ParseBothChannels)
CustomParser::CustomParser(const CustomParserSettings &settings)
{
setObjectName(QLatin1String("CustomParser"));
@@ -64,91 +127,60 @@ CustomParser::~CustomParser()
{
}
void CustomParser::setErrorPattern(const QString &errorPattern)
{
m_errorRegExp.setPattern(errorPattern);
QTC_CHECK(m_errorRegExp.isValid());
}
QString CustomParser::errorPattern() const
{
return m_errorRegExp.pattern();
}
int CustomParser::lineNumberCap() const
{
return m_lineNumberCap;
}
void CustomParser::setLineNumberCap(int lineNumberCap)
{
m_lineNumberCap = lineNumberCap;
}
int CustomParser::fileNameCap() const
{
return m_fileNameCap;
}
void CustomParser::setFileNameCap(int fileNameCap)
{
m_fileNameCap = fileNameCap;
}
int CustomParser::messageCap() const
{
return m_messageCap;
}
void CustomParser::setMessageCap(int messageCap)
{
m_messageCap = messageCap;
}
void CustomParser::stdError(const QString &line)
{
if (m_parserChannels & ParseStdErrChannel)
if (parseLine(line))
return;
if (parseLine(line, CustomParserExpression::ParseStdErrChannel))
return;
IOutputParser::stdError(line);
}
void CustomParser::stdOutput(const QString &line)
{
if (m_parserChannels & ParseStdOutChannel)
if (parseLine(line))
return;
if (parseLine(line, CustomParserExpression::ParseStdOutChannel))
return;
IOutputParser::stdOutput(line);
}
void CustomParser::setSettings(const CustomParserSettings &settings)
{
setErrorPattern(settings.errorPattern);
setFileNameCap(settings.fileNameCap);
setLineNumberCap(settings.lineNumberCap);
setMessageCap(settings.messageCap);
m_error = settings.error;
m_warning = settings.warning;
}
bool CustomParser::parseLine(const QString &rawLine)
bool CustomParser::hasMatch(const QString &line, CustomParserExpression::CustomParserChannel channel,
const CustomParserExpression &expression, Task::TaskType taskType)
{
if (m_errorRegExp.pattern().isEmpty())
if (!(channel & expression.channel()))
return false;
const QRegularExpressionMatch match = m_errorRegExp.match(rawLine.trimmed());
if (expression.pattern().isEmpty())
return false;
const QRegularExpressionMatch match = expression.match(line);
if (!match.hasMatch())
return false;
const FileName fileName = FileName::fromUserInput(match.captured(m_fileNameCap));
const int lineNumber = match.captured(m_lineNumberCap).toInt();
const QString message = match.captured(m_messageCap);
const FileName fileName = FileName::fromUserInput(match.captured(expression.fileNameCap()));
const int lineNumber = match.captured(expression.lineNumberCap()).toInt();
const QString message = match.captured(expression.messageCap());
Task task = Task(Task::Error, message, fileName, lineNumber, Constants::TASK_CATEGORY_COMPILE);
const Task task = Task(taskType, message, fileName, lineNumber, Constants::TASK_CATEGORY_COMPILE);
emit addTask(task, 1);
return true;
}
bool CustomParser::parseLine(const QString &rawLine, CustomParserExpression::CustomParserChannel channel)
{
const QString line = rawLine.trimmed();
if (hasMatch(line, channel, m_error, Task::Error))
return true;
return hasMatch(line, channel, m_warning, Task::Warning);
}
// Unit tests:
#ifdef WITH_TESTS
@@ -162,10 +194,16 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<OutputParserTester::Channel>("inputChannel");
QTest::addColumn<QString>("pattern");
QTest::addColumn<int>("fileNameCap");
QTest::addColumn<int>("lineNumberCap");
QTest::addColumn<int>("messageCap");
QTest::addColumn<CustomParserExpression::CustomParserChannel>("filterErrorChannel");
QTest::addColumn<CustomParserExpression::CustomParserChannel>("filterWarningChannel");
QTest::addColumn<QString>("errorPattern");
QTest::addColumn<int>("errorFileNameCap");
QTest::addColumn<int>("errorLineNumberCap");
QTest::addColumn<int>("errorMessageCap");
QTest::addColumn<QString>("warningPattern");
QTest::addColumn<int>("warningFileNameCap");
QTest::addColumn<int>("warningLineNumberCap");
QTest::addColumn<int>("warningMessageCap");
QTest::addColumn<QString>("childStdOutLines");
QTest::addColumn<QString>("childStdErrLines");
QTest::addColumn<QList<Task> >("tasks");
@@ -175,11 +213,12 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
const QString simplePattern = QLatin1String("^([a-z]+\\.[a-z]+):(\\d+): error: ([^\\s].+)$");
const FileName fileName = FileName::fromUserInput(QLatin1String("main.c"));
QTest::newRow("empty pattern")
QTest::newRow("empty patterns")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDOUT
<< QString::fromLatin1("")
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< QString() << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString::fromLatin1("Sometext\n") << QString()
<< QList<Task>()
<< QString();
@@ -187,8 +226,9 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("pass-through stdout")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDOUT
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString::fromLatin1("Sometext\n") << QString()
<< QList<Task>()
<< QString();
@@ -196,35 +236,59 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("pass-through stderr")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDERR
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString::fromLatin1("Sometext\n")
<< QList<Task>()
<< QString();
const QString simpleError = QLatin1String("main.c:9: error: `sfasdf' undeclared (first use this function)");
const QString simpleErrorPassThrough = simpleError + QLatin1Char('\n');
const QString message = QLatin1String("`sfasdf' undeclared (first use this function)");
QTest::newRow("simple error")
<< simpleError
<< OutputParserTester::STDERR
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, 9, categoryCompile)
)
<< QString();
const QString simpleError2 = QLatin1String("Error: main.c:19: `sfasdf' undeclared (first use this function)");
const QString simplePattern2 = QLatin1String("^Error: ([a-z]+\\.[a-z]+):(\\d+): ([^\\s].+)$");
QTest::newRow("simple error on wrong channel")
<< simpleError
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseStdErrChannel << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< simpleErrorPassThrough << QString()
<< QList<Task>()
<< QString();
QTest::newRow("simple error on other wrong channel")
<< simpleError
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseStdOutChannel << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< QString() << simpleErrorPassThrough
<< QList<Task>()
<< QString();
const QString simpleError2 = QLatin1String("Error: Line 19 in main.c: `sfasdf' undeclared (first use this function)");
const QString simplePattern2 = QLatin1String("^Error: Line (\\d+) in ([a-z]+\\.[a-z]+): ([^\\s].+)$");
const int lineNumber2 = 19;
QTest::newRow("another simple error on stderr")
<< simpleError2
<< OutputParserTester::STDERR
<< simplePattern2
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern2 << 2 << 1 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, lineNumber2, categoryCompile)
@@ -234,14 +298,91 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("another simple error on stdout")
<< simpleError2
<< OutputParserTester::STDOUT
<< simplePattern2
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern2 << 2 << 1 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, lineNumber2, categoryCompile)
)
<< QString();
const QString simpleWarningPattern = QLatin1String("^([a-z]+\\.[a-z]+):(\\d+): warning: ([^\\s].+)$");
const QString simpleWarning = QLatin1String("main.c:1234: warning: `helloWorld' declared but not used");
const QString warningMessage = QLatin1String("`helloWorld' declared but not used");
QTest::newRow("simple warning")
<< simpleWarning
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< QString() << 1 << 2 << 3
<< simpleWarningPattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, 1234, categoryCompile)
)
<< QString();
const QString simpleWarning2 = QLatin1String("Warning: `helloWorld' declared but not used (main.c:19)");
const QString simpleWarningPassThrough2 = simpleWarning2 + QLatin1Char('\n');
const QString simpleWarningPattern2 = QLatin1String("^Warning: (.*) \\(([a-z]+\\.[a-z]+):(\\d+)\\)$");
QTest::newRow("another simple warning on stdout")
<< simpleWarning2
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdOutChannel
<< simplePattern2 << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, lineNumber2, categoryCompile)
)
<< QString();
QTest::newRow("warning on wrong channel")
<< simpleWarning2
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdErrChannel
<< QString() << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< simpleWarningPassThrough2 << QString()
<< QList<Task>()
<< QString();
QTest::newRow("warning on other wrong channel")
<< simpleWarning2
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdOutChannel
<< QString() << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< QString() << simpleWarningPassThrough2
<< QList<Task>()
<< QString();
QTest::newRow("error and *warning*")
<< simpleWarning
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< simpleWarningPattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, 1234, categoryCompile)
)
<< QString();
QTest::newRow("*error* when equal pattern")
<< simpleError
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< simplePattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, 9, categoryCompile)
)
<< QString();
const QString unitTestError = QLatin1String("../LedDriver/LedDriverTest.c:63: FAIL: Expected 0x0080 Was 0xffff");
const FileName unitTestFileName = FileName::fromUserInput(QLatin1String("../LedDriver/LedDriverTest.c"));
const QString unitTestMessage = QLatin1String("Expected 0x0080 Was 0xffff");
@@ -251,8 +392,9 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("unit test error")
<< unitTestError
<< OutputParserTester::STDOUT
<< unitTestPattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< unitTestPattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, unitTestMessage, unitTestFileName, unitTestLineNumber, categoryCompile)
@@ -264,20 +406,35 @@ void ProjectExplorerPlugin::testCustomOutputParsers()
{
QFETCH(QString, input);
QFETCH(OutputParserTester::Channel, inputChannel);
QFETCH(QString, pattern);
QFETCH(int, fileNameCap);
QFETCH(int, lineNumberCap);
QFETCH(int, messageCap);
QFETCH(CustomParserExpression::CustomParserChannel, filterErrorChannel);
QFETCH(CustomParserExpression::CustomParserChannel, filterWarningChannel);
QFETCH(QString, errorPattern);
QFETCH(int, errorFileNameCap);
QFETCH(int, errorLineNumberCap);
QFETCH(int, errorMessageCap);
QFETCH(QString, warningPattern);
QFETCH(int, warningFileNameCap);
QFETCH(int, warningLineNumberCap);
QFETCH(int, warningMessageCap);
QFETCH(QString, childStdOutLines);
QFETCH(QString, childStdErrLines);
QFETCH(QList<Task>, tasks);
QFETCH(QString, outputLines);
CustomParserSettings settings;
settings.error.setPattern(errorPattern);
settings.error.setFileNameCap(errorFileNameCap);
settings.error.setLineNumberCap(errorLineNumberCap);
settings.error.setMessageCap(errorMessageCap);
settings.error.setChannel(filterErrorChannel);
settings.warning.setPattern(warningPattern);
settings.warning.setFileNameCap(warningFileNameCap);
settings.warning.setLineNumberCap(warningLineNumberCap);
settings.warning.setMessageCap(warningMessageCap);
settings.warning.setChannel(filterWarningChannel);
CustomParser *parser = new CustomParser;
parser->setErrorPattern(pattern);
parser->setFileNameCap(fileNameCap);
parser->setLineNumberCap(lineNumberCap);
parser->setMessageCap(messageCap);
parser->setSettings(settings);
OutputParserTester testbench;
testbench.appendOutputParser(parser);