diff --git a/kdevplatform/outputview/outputfilteringstrategies.cpp b/kdevplatform/outputview/outputfilteringstrategies.cpp
index 6a2599e406..2a9ca1cdde 100644
--- a/kdevplatform/outputview/outputfilteringstrategies.cpp
+++ b/kdevplatform/outputview/outputfilteringstrategies.cpp
@@ -1,458 +1,458 @@
/*
This file is part of KDevelop
Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "outputfilteringstrategies.h"
#include "outputformats.h"
#include "filtereditem.h"
#include
#include
#include
#include
namespace KDevelop
{
void initializeFilteredItem(FilteredItem& item, const ErrorFormat& filter, const QRegularExpressionMatch& match)
{
item.lineNo = match.captured( filter.lineGroup ).toInt() - 1;
item.columnNo = filter.columnNumber(match);
}
template
FilteredItem match(const ErrorFormats& errorFormats, const QString& line)
{
FilteredItem item(line);
for( const ErrorFormat& curErrFilter : errorFormats ) {
const auto match = curErrFilter.expression.match(line);
if( match.hasMatch() ) {
initializeFilteredItem(item, curErrFilter, match);
item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup ));
item.type = FilteredItem::ErrorItem;
// Make the item clickable if it comes with the necessary file & line number information
if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) {
item.isActivatable = true;
}
break;
}
}
return item;
}
/// --- No filter strategy ---
NoFilterStrategy::NoFilterStrategy()
{
}
FilteredItem NoFilterStrategy::actionInLine(const QString& line)
{
return FilteredItem( line );
}
FilteredItem NoFilterStrategy::errorInLine(const QString& line)
{
return FilteredItem( line );
}
/// --- Compiler error filter strategy ---
/// Impl. of CompilerFilterStrategy.
class CompilerFilterStrategyPrivate
{
public:
explicit CompilerFilterStrategyPrivate(const QUrl& buildDir);
Path pathForFile( const QString& ) const;
bool isMultiLineCase(const ErrorFormat& curErrFilter) const;
void putDirAtEnd(const Path& pathToInsert);
QVector m_currentDirs;
Path m_buildDir;
using PositionMap = QHash;
PositionMap m_positionInCurrentDirs;
};
CompilerFilterStrategyPrivate::CompilerFilterStrategyPrivate(const QUrl& buildDir)
: m_buildDir(buildDir)
{
}
Path CompilerFilterStrategyPrivate::pathForFile(const QString& filename) const
{
QFileInfo fi( filename );
Path currentPath;
if( fi.isRelative() ) {
if( m_currentDirs.isEmpty() ) {
return Path(m_buildDir, filename );
}
auto it = m_currentDirs.constEnd() - 1;
do {
currentPath = Path(*it, filename);
} while( (it-- != m_currentDirs.constBegin()) && !QFileInfo::exists(currentPath.toLocalFile()) );
return currentPath;
} else {
currentPath = Path(filename);
}
return currentPath;
}
bool CompilerFilterStrategyPrivate::isMultiLineCase(const KDevelop::ErrorFormat& curErrFilter) const
{
if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) {
return true;
}
return false;
}
void CompilerFilterStrategyPrivate::putDirAtEnd(const Path& pathToInsert)
{
CompilerFilterStrategyPrivate::PositionMap::iterator it = m_positionInCurrentDirs.find( pathToInsert );
// Encountered new build directory?
if (it == m_positionInCurrentDirs.end()) {
m_currentDirs.push_back( pathToInsert );
m_positionInCurrentDirs.insert( pathToInsert, m_currentDirs.size() - 1 );
} else {
// Build dir already in currentDirs, but move it to back of currentDirs list
// (this gives us most-recently-used semantics in pathForFile)
std::rotate(m_currentDirs.begin() + it.value(), m_currentDirs.begin() + it.value() + 1, m_currentDirs.end() );
it.value() = m_currentDirs.size() - 1;
}
}
CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir)
: d(new CompilerFilterStrategyPrivate( buildDir ))
{
}
CompilerFilterStrategy::~CompilerFilterStrategy() = default;
QVector CompilerFilterStrategy::getCurrentDirs()
{
QVector ret;
ret.reserve(d->m_currentDirs.size());
foreach (const auto& path, d->m_currentDirs) {
ret << path.pathOrUrl();
}
return ret;
}
FilteredItem CompilerFilterStrategy::actionInLine(const QString& line)
{
// A list of filters for possible compiler, linker, and make actions
static const ActionFormat ACTION_FILTERS[] = {
ActionFormat( 2,
QStringLiteral("(?:^|[^=])\\b(gcc|CC|cc|distcc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\s+.*-c.*[/ '\\\\]+(\\w+\\.(?:cpp|CPP|c|C|cxx|CXX|cs|java|hpf|f|F|f90|F90|f95|F95))")),
//moc and uic
ActionFormat( 2, QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")),
//libtool linking
ActionFormat( QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ),
//unsermake
ActionFormat( 1, QStringLiteral("^compiling (.*)") ),
ActionFormat( 2, QStringLiteral("^generating (.*)") ),
ActionFormat( 2, QStringLiteral("(gcc|cc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)")),
ActionFormat( 2, QStringLiteral("^linking (.*)") ),
//cmake
ActionFormat( 1, QStringLiteral("\\[.+%\\] Built target (.*)") ),
ActionFormat( QStringLiteral("cmake"),
QStringLiteral("\\[.+%\\] Building .* object (.*)"), 1 ),
ActionFormat( 1, QStringLiteral("\\[.+%\\] Generating (.*)") ),
ActionFormat( 1, QStringLiteral("^Linking (.*)") ),
ActionFormat( QStringLiteral("cmake"),
QStringLiteral("(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)"), -1 ),
ActionFormat( 1, QStringLiteral("-- Installing (.*)") ),
//cmake - cd - filter for project directory
ActionFormat( QStringLiteral("cd"),
QStringLiteral("(?:)cmake (?:.*?) (/.*$)"), 1),
//libtool install
ActionFormat( {},
QStringLiteral("/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)"), 1 ),
ActionFormat( {},
QStringLiteral("/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)"), 1 ),
//dcop
ActionFormat( QStringLiteral("dcopidl"),
QStringLiteral("dcopidl .* > ([^\\s;]+)"), 1 ),
ActionFormat( QStringLiteral("dcopidl2cpp"),
QStringLiteral("dcopidl2cpp (?:\\S* )*([^\\s;]+)"), 1 ),
// match against Entering directory to update current build dir
ActionFormat( QStringLiteral("cd"),
QStringLiteral("make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'"), 2),
// waf and scons use the same basic convention as make
ActionFormat( QStringLiteral("cd"),
QStringLiteral("(Waf|scons): Entering directory (\\`|\\')(.+)'"), 3)
};
FilteredItem item(line);
for (const auto& curActFilter : ACTION_FILTERS) {
const auto match = curActFilter.expression.match(line);
if( match.hasMatch() ) {
item.type = FilteredItem::ActionItem;
if( curActFilter.tool == QLatin1String("cd") ) {
const Path path(match.captured(curActFilter.fileGroup));
d->m_currentDirs.push_back( path );
d->m_positionInCurrentDirs.insert( path , d->m_currentDirs.size() - 1 );
}
// Special case for cmake: we parse the "Compiling " expression
// and use it to find out about the build paths encountered during a build.
// They are later searched by pathForFile to find source files corresponding to
// compiler errors.
// Note: CMake objectfile has the format: "/path/to/four/CMakeFiles/file.o"
if ( curActFilter.fileGroup != -1 && curActFilter.tool == QLatin1String("cmake") && line.contains(QStringLiteral("Building"))) {
const auto objectFile = match.captured(curActFilter.fileGroup);
const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0);
d->putDirAtEnd(Path(d->m_buildDir, dir));
}
break;
}
}
return item;
}
FilteredItem CompilerFilterStrategy::errorInLine(const QString& line)
{
// All the possible string that indicate an error if we via Regex have been able to
// extract file and linenumber from a given outputline
// TODO: This seems clumsy -- and requires another scan of the line.
// Merge this information into ErrorFormat? --Kevin
using Indicator = QPair;
static const Indicator INDICATORS[] = {
// ld
Indicator(QStringLiteral("undefined reference"), FilteredItem::ErrorItem),
Indicator(QStringLiteral("undefined symbol"), FilteredItem::ErrorItem),
Indicator(QStringLiteral("ld: cannot find"), FilteredItem::ErrorItem),
Indicator(QStringLiteral("no such file"), FilteredItem::ErrorItem),
// gcc
Indicator(QStringLiteral("error"), FilteredItem::ErrorItem),
// generic
Indicator(QStringLiteral("warning"), FilteredItem::WarningItem),
Indicator(QStringLiteral("info"), FilteredItem::InformationItem),
Indicator(QStringLiteral("note"), FilteredItem::InformationItem),
};
// A list of filters for possible compiler, linker, and make errors
static const ErrorFormat ERROR_FILTERS[] = {
#ifdef Q_OS_WIN
// MSVC
ErrorFormat( QStringLiteral("^([a-zA-Z]:\\\\.+)\\(([1-9][0-9]*)\\): ((?:error|warning) .+\\:).*$"), 1, 2, 3 ),
#endif
// GCC - another case, eg. for #include "pixmap.xpm" which does not exists
ErrorFormat( QStringLiteral("^([^:\\t]+):([0-9]+):([0-9]+):([^0-9]+)"), 1, 2, 4, 3 ),
// ant
ErrorFormat( QStringLiteral("\\[javac\\][\\s]+([^:\\t]+):([0-9]+): (warning: .*|error: .*)"), 1, 2, 3, QStringLiteral("javac")),
// GCC
ErrorFormat( QStringLiteral("^([^:\\t]+):([0-9]+):([^0-9]+)"), 1, 2, 3 ),
// GCC
ErrorFormat( QStringLiteral("^(In file included from |[ ]+from )([^:\\t]+):([0-9]+)(:|,)(|[0-9]+)"), 2, 3, 5 ),
// ICC
ErrorFormat( QStringLiteral("^([^:\\t]+)\\(([0-9]+)\\):([^0-9]+)"), 1, 2, 3, QStringLiteral("intel") ),
//libtool link
ErrorFormat( QStringLiteral("^(libtool):( link):( warning): "), 0, 0, 0 ),
// make
ErrorFormat( QStringLiteral("No rule to make target"), 0, 0, 0 ),
- // cmake
- ErrorFormat( QStringLiteral("^([^:\\t]+):([0-9]+):"), 1, 2, 0, QStringLiteral("cmake") ),
+ // cmake - multiline expression
+ ErrorFormat( QStringLiteral("(^\\/[\\w|\\/| |\\.]+):([0-9]+):"), 1, 2, 0, QStringLiteral("cmake") ),
// cmake
ErrorFormat( QStringLiteral("CMake (Error|Warning) (|\\([a-zA-Z]+\\) )(in|at) ([^:]+):($|[0-9]+)"), 4, 5, 1, QStringLiteral("cmake") ),
// cmake/automoc
// example: AUTOMOC: error: /foo/bar.cpp The file includes (...),
// example: AUTOMOC: error: /foo/bar.cpp: The file includes (...)
// note: ':' after file name isn't always appended, see http://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=317d8498aa02c9f486bf5071963bb2034777cdd6
// example: AUTOGEN: error: /foo/bar.cpp: The file includes (...)
// note: AUTOMOC got renamed to AUTOGEN at some point
ErrorFormat( QStringLiteral("^(AUTOMOC|AUTOGEN): error: ([^:]+):? (The file .*)$"), 2, 0, 0 ),
// via qt4_automoc
// example: automoc4: The file "/foo/bar.cpp" includes the moc file "bar1.moc", but ...
ErrorFormat( QStringLiteral("^automoc4: The file \"([^\"]+)\" includes the moc file"), 1, 0, 0 ),
// Fortran
ErrorFormat( QStringLiteral("\"(.*)\", line ([0-9]+):(.*)"), 1, 2, 3 ),
// GFortran
ErrorFormat( QStringLiteral("^(.*):([0-9]+)\\.([0-9]+):(.*)"), 1, 2, 4, QStringLiteral("gfortran"), 3 ),
// Jade
ErrorFormat( QStringLiteral("^[a-zA-Z]+:([^:\\t]+):([0-9]+):[0-9]+:[a-zA-Z]:(.*)"), 1, 2, 3 ),
// ifort
ErrorFormat( QStringLiteral("^fortcom: (.*): (.*), line ([0-9]+):(.*)"), 2, 3, 1, QStringLiteral("intel") ),
// PGI
ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-(.*) \\((.*): ([0-9]+)\\)"), 5, 6, 4, QStringLiteral("pgi") ),
// PGI (2)
ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-Symbol, (.*) \\((.*)\\)"), 5, 5, 4, QStringLiteral("pgi") ),
};
FilteredItem item(line);
for (const auto& curErrFilter : ERROR_FILTERS) {
const auto match = curErrFilter.expression.match(line);
if( match.hasMatch() && !( line.contains( QLatin1String("Each undeclared identifier is reported only once") )
|| line.contains( QLatin1String("for each function it appears in.") ) ) )
{
if(curErrFilter.fileGroup > 0) {
if( curErrFilter.compiler == QLatin1String("cmake") ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this
if( d->m_currentDirs.empty() ) {
d->putDirAtEnd( d->m_buildDir.parent() );
}
}
item.url = d->pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl();
}
initializeFilteredItem(item, curErrFilter, match);
const QString txt = match.captured(curErrFilter.textGroup);
// Find the indicator which happens most early.
int earliestIndicatorIdx = txt.length();
for (const auto& curIndicator : INDICATORS) {
int curIndicatorIdx = txt.indexOf(curIndicator.first, 0, Qt::CaseInsensitive);
if((curIndicatorIdx >= 0) && (earliestIndicatorIdx > curIndicatorIdx)) {
earliestIndicatorIdx = curIndicatorIdx;
item.type = curIndicator.second;
}
}
// Make the item clickable if it comes with the necessary file information
if (item.url.isValid()) {
item.isActivatable = true;
if(item.type == FilteredItem::InvalidItem) {
// If there are no error indicators in the line
// maybe this is a multiline case
if(d->isMultiLineCase(curErrFilter)) {
item.type = FilteredItem::ErrorItem;
} else {
// Okay so we couldn't find anything to indicate an error, but we have file and lineGroup
// Lets keep this item clickable and indicate this to the user.
item.type = FilteredItem::InformationItem;
}
}
}
break;
}
}
return item;
}
/// --- Script error filter strategy ---
ScriptErrorFilterStrategy::ScriptErrorFilterStrategy()
{
}
FilteredItem ScriptErrorFilterStrategy::actionInLine(const QString& line)
{
return FilteredItem(line);
}
FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line)
{
// A list of filters for possible Python and PHP errors
static const ErrorFormat SCRIPT_ERROR_FILTERS[] = {
ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ),
ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ),
ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 )
};
return match(SCRIPT_ERROR_FILTERS, line);
}
/// --- Native application error filter strategy ---
NativeAppErrorFilterStrategy::NativeAppErrorFilterStrategy()
{
}
FilteredItem NativeAppErrorFilterStrategy::actionInLine(const QString& line)
{
return FilteredItem(line);
}
FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line)
{
static const ErrorFormat NATIVE_APPLICATION_ERROR_FILTERS[] = {
// BEGIN: C++
// a.out: test.cpp:5: int main(): Assertion `false' failed.
ErrorFormat(QStringLiteral("^.+: (.+):([1-9][0-9]*): .*: Assertion `.*' failed\\.$"), 1, 2, -1),
// END: C++
// BEGIN: Qt
// Note: Scan the whole line for catching Qt runtime warnings (-> no ^$)
// Reasoning: With QT_MESSAGE_PATTERN you can configure the output to your desire,
// which means the output could be arbitrarily prefixed or suffixed with other content
// QObject::connect related errors, also see err_method_notfound() in qobject.cpp
// QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313
ErrorFormat(QStringLiteral("QObject::connect: (?:No such|Parentheses expected,) (?:slot|signal) [^ ]* in (.*):([0-9]+)"), 1, 2, -1),
// ASSERT: "errors().isEmpty()" in file /foo/bar.cpp, line 49
ErrorFormat(QStringLiteral("ASSERT: \"(.*)\" in file (.*), line ([0-9]+)"), 2, 3, -1),
// Catch:
// FAIL! : FooTest::testBar() Compared pointers are not the same
// Actual ...
// Expected ...
// Loc: [/foo/bar.cpp(33)]
//
// Do *not* catch:
// ...
// Loc: [Unknown file(0)]
ErrorFormat(QStringLiteral(" Loc: \\[(.*)\\(([1-9][0-9]*)\\)\\]"), 1, 2, -1),
// file:///path/to/foo.qml:7:1: Bar is not a type
// file:///path/to/foo.qml:49:5: QML Row: Binding loop detected for property "height"
ErrorFormat(QStringLiteral("(file:\\/\\/(?:[^:]+)):([1-9][0-9]*):([1-9][0-9]*): (.*) (?:is not a type|is ambiguous|is instantiated recursively|Binding loop detected)"), 1, 2, -1, 3),
// file:///path/to/foo.qml:52: TypeError: Cannot read property 'height' of null
ErrorFormat(QStringLiteral("(file:\\/\\/(?:[^:]+)):([1-9][0-9]*): ([a-zA-Z]+)Error"), 1, 2, -1),
// END: Qt
};
return match(NATIVE_APPLICATION_ERROR_FILTERS, line);
}
/// --- Static Analysis filter strategy ---
StaticAnalysisFilterStrategy::StaticAnalysisFilterStrategy()
{
}
FilteredItem StaticAnalysisFilterStrategy::actionInLine(const QString& line)
{
return FilteredItem(line);
}
FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line)
{
// A list of filters for static analysis tools (krazy2, cppcheck)
static const ErrorFormat STATIC_ANALYSIS_FILTERS[] = {
// CppCheck
ErrorFormat( QStringLiteral("^\\[(.*):([0-9]+)\\]:(.*)"), 1, 2, 3 ),
// krazy2
ErrorFormat( QStringLiteral("^\\t([^:]+).*line#([0-9]+).*"), 1, 2, -1 ),
// krazy2 without line info
ErrorFormat( QStringLiteral("^\\t(.*): missing license"), 1, -1, -1 )
};
return match(STATIC_ANALYSIS_FILTERS, line);
}
}
diff --git a/kdevplatform/outputview/tests/test_filteringstrategy.cpp b/kdevplatform/outputview/tests/test_filteringstrategy.cpp
index af289c1534..10874bf6fe 100644
--- a/kdevplatform/outputview/tests/test_filteringstrategy.cpp
+++ b/kdevplatform/outputview/tests/test_filteringstrategy.cpp
@@ -1,537 +1,540 @@
/*
This file is part of KDevelop
Copyright 2012 Milian Wolff
Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "test_filteringstrategy.h"
#include "testlinebuilderfunctions.h"
#include
#include
#include
using namespace KDevelop;
QTEST_GUILESS_MAIN(TestFilteringStrategy)
namespace QTest {
template<>
inline char* toString(const FilteredItem::FilteredOutputItemType& type)
{
switch (type) {
case FilteredItem::ActionItem:
return qstrdup("ActionItem");
case FilteredItem::CustomItem:
return qstrdup("CustomItem");
case FilteredItem::ErrorItem:
return qstrdup("ErrorItem");
case FilteredItem::InformationItem:
return qstrdup("InformationItem");
case FilteredItem::InvalidItem:
return qstrdup("InvalidItem");
case FilteredItem::StandardItem:
return qstrdup("StandardItem");
case FilteredItem::WarningItem:
return qstrdup("WarningItem");
}
return qstrdup("unknown");
}
inline QTestData& newRowForPathType(const char *dataTag, TestPathType pathType)
{
if (pathType == UnixFilePathWithSpaces) {
return QTest::newRow(QString(QLatin1String(dataTag)+QLatin1String("-ws")).toUtf8().constData());
}
return QTest::newRow(dataTag);
}
}
void TestFilteringStrategy::testNoFilterStrategy_data()
{
QTest::addColumn("line");
QTest::addColumn("expected");
QTest::newRow("cppcheck-info-line")
<< buildCppCheckInformationLine() << FilteredItem::InvalidItem;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("cppcheck-error-line", pathType)
<< buildCppCheckErrorLine(pathType) << FilteredItem::InvalidItem;
QTest::newRowForPathType("compiler-line", pathType)
<< buildCompilerLine(pathType) << FilteredItem::InvalidItem;
QTest::newRowForPathType("compiler-error-line", pathType)
<< buildCompilerErrorLine(pathType) << FilteredItem::InvalidItem;
}
QTest::newRow("compiler-action-line")
<< buildCompilerActionLine() << FilteredItem::InvalidItem;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("compiler-information-line", pathType)
<< buildCompilerInformationLine(pathType) << FilteredItem::InvalidItem;
QTest::newRowForPathType("python-error-line", pathType)
<< buildPythonErrorLine(pathType) << FilteredItem::InvalidItem;
}
}
void TestFilteringStrategy::testNoFilterStrategy()
{
QFETCH(QString, line);
QFETCH(FilteredItem::FilteredOutputItemType, expected);
NoFilterStrategy testee;
FilteredItem item1 = testee.errorInLine(line);
QCOMPARE(item1.type, expected);
item1 = testee.actionInLine(line);
QCOMPARE(item1.type, expected);
}
void TestFilteringStrategy::testCompilerFilterStrategy_data()
{
QTest::addColumn("line");
QTest::addColumn("expectedError");
QTest::addColumn("expectedAction");
QTest::addColumn("pathType");
QTest::newRow("cppcheck-info-line")
<< buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("cppcheck-error-line", pathType)
<< buildCppCheckErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-line", pathType)
<< buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-error-line", pathType)
<< buildCompilerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-information-line", pathType)
<< buildCompilerInformationLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-information-line2", pathType)
<< buildInfileIncludedFromFirstLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-information-line3", pathType)
<< buildInfileIncludedFromSecondLine(pathType) << FilteredItem::InformationItem << FilteredItem::InvalidItem << pathType;
}
QTest::newRow("cmake-error-line1")
<< "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
QTest::newRow("cmake-error-multiline1")
<< "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("cmake-error-multiline2", pathType)
<< buildCmakeConfigureMultiLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
}
QTest::newRow("cmake-warning-line")
<< "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
QTest::newRow("cmake-automoc-error")
<< "AUTOMOC: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
QTest::newRow("cmake-automoc4-error")
<< "automoc4: The file \"/foo/bar.cpp\" includes the moc file \"bar1.moc\"" << FilteredItem::InformationItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
QTest::newRow("cmake-autogen-error")
<< "AUTOGEN: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
QTest::newRow("linker-action-line")
<< "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem << UnixFilePathNoSpaces;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("linker-error-line", pathType)
<< buildLinkerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("python-error-line", pathType)
<< buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
}
}
void TestFilteringStrategy::testCompilerFilterStrategy()
{
QFETCH(QString, line);
QFETCH(FilteredItem::FilteredOutputItemType, expectedError);
QFETCH(FilteredItem::FilteredOutputItemType, expectedAction);
QFETCH(TestPathType, pathType);
QUrl projecturl = QUrl::fromLocalFile( projectPath(pathType) );
CompilerFilterStrategy testee(projecturl);
FilteredItem item1 = testee.errorInLine(line);
QCOMPARE(item1.type, expectedError);
item1 = testee.actionInLine(line);
QCOMPARE(item1.type, expectedAction);
}
void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords_data()
{
QTest::addColumn("line");
QTest::addColumn("expectedError");
QTest::addColumn("expectedAction");
QTest::newRow("warning-containing-error-word")
<< "RingBuffer.cpp:64:6: warning: unused parameter ‘errorItem’ [-Wunused-parameter]"
<< FilteredItem::WarningItem << FilteredItem::InvalidItem;
QTest::newRow("error-containing-info-word")
<< "NodeSet.hpp:89:27: error: ‘Info’ was not declared in this scope"
<< FilteredItem::ErrorItem << FilteredItem::InvalidItem;
QTest::newRow("warning-in-filename-containing-error-word")
<< "ErrorHandling.cpp:100:56: warning: unused parameter ‘item’ [-Wunused-parameter]"
<< FilteredItem::WarningItem << FilteredItem::InvalidItem;
QTest::newRow("error-in-filename-containing-warning-word")
<< "WarningHandling.cpp:100:56: error: ‘Item’ was not declared in this scope"
<< FilteredItem::ErrorItem << FilteredItem::InvalidItem;
}
void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords()
{
QFETCH(QString, line);
QFETCH(FilteredItem::FilteredOutputItemType, expectedError);
QFETCH(FilteredItem::FilteredOutputItemType, expectedAction);
QUrl projecturl = QUrl::fromLocalFile( projectPath() );
CompilerFilterStrategy testee(projecturl);
FilteredItem item1 = testee.errorInLine(line);
QCOMPARE(item1.type, expectedError);
item1 = testee.actionInLine(line);
QCOMPARE(item1.type, expectedAction);
}
void TestFilteringStrategy::testScriptErrorFilterStrategy_data()
{
QTest::addColumn("line");
QTest::addColumn("expectedError");
QTest::addColumn("expectedAction");
QTest::newRow("cppcheck-info-line")
<< buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("cppcheck-error-line", pathType)
<< buildCppCheckErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem;
QTest::newRowForPathType("compiler-line", pathType)
<< buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem;
QTest::newRowForPathType("compiler-error-line", pathType)
<< buildCompilerErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem;
}
QTest::newRow("compiler-action-line")
<< "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("python-error-line", pathType)
<< buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem;
}
}
void TestFilteringStrategy::testScriptErrorFilterStrategy()
{
QFETCH(QString, line);
QFETCH(FilteredItem::FilteredOutputItemType, expectedError);
QFETCH(FilteredItem::FilteredOutputItemType, expectedAction);
ScriptErrorFilterStrategy testee;
FilteredItem item1 = testee.errorInLine(line);
QCOMPARE(item1.type, expectedError);
item1 = testee.actionInLine(line);
QCOMPARE(item1.type, expectedAction);
}
void TestFilteringStrategy::testNativeAppErrorFilterStrategy_data()
{
QTest::addColumn("line");
QTest::addColumn("file");
QTest::addColumn("lineNo");
QTest::addColumn("column");
QTest::addColumn("itemtype");
// BEGIN: C++
QTest::newRow("cassert")
<< "a.out: /foo/bar/test.cpp:5: int main(): Assertion `false' failed."
<< "/foo/bar/test.cpp"
<< 4 << 0 << FilteredItem::ErrorItem;
// END: C++
// BEGIN: Qt
// TODO: qt-connect-* and friends shouldn't be error items but warnings items instead
// this needs refactoring in outputfilteringstrategies, though...
QTest::newRow("qt-connect-nosuch-slot")
<< "QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313"
<< "/foo/bar.cpp"
<< 312 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qt-connect-nosuch-signal")
<< "QObject::connect: No such signal Foo::bar() in /foo/bar.cpp:313"
<< "/foo/bar.cpp"
<< 312 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qt-connect-parentheses-slot")
<< "QObject::connect: Parentheses expected, slot Foo::bar() in /foo/bar.cpp:313"
<< "/foo/bar.cpp"
<< 312 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qt-connect-parentheses-signal")
<< "QObject::connect: Parentheses expected, signal Foo::bar() in /foo/bar.cpp:313"
<< "/foo/bar.cpp"
<< 312 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qt-assert")
<< "ASSERT: \"errors().isEmpty()\" in file /tmp/foo/bar.cpp, line 49"
<< "/tmp/foo/bar.cpp"
<< 48 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qttest-assert")
<< "QFATAL : FooTest::testBar() ASSERT: \"index.isValid()\" in file /foo/bar.cpp, line 32"
<< "/foo/bar.cpp"
<< 31 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qttest-loc")
<< " Loc: [/foo/bar.cpp(33)]"
<< "/foo/bar.cpp"
<< 32 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qttest-loc-nocatch")
<< " Loc: [Unknown file(0)]"
<< ""
<< -1 << -1 << FilteredItem::InvalidItem;
QTest::newRow("qml-import-unix")
<< "file:///path/to/foo.qml:7:1: Bar is not a type"
<< "/path/to/foo.qml"
<< 6 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qml-import-unix1")
<< "file:///path/to/foo.qml:7:1: Bar is ambiguous. Found in A and in B"
<< "/path/to/foo.qml"
<< 6 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qml-import-unix2")
<< "file:///path/to/foo.qml:7:1: Bar is instantiated recursively"
<< "/path/to/foo.qml"
<< 6 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qml-typeerror")
<< "file:///path/to/foo.qml:7: TypeError: Cannot read property 'height' of null"
<< "/path/to/foo.qml"
<< 6 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qml-referenceerror")
<< "file:///path/to/foo.qml:7: ReferenceError: readOnly is not defined"
<< "/path/to/foo.qml"
<< 6 << 0 << FilteredItem::ErrorItem;
QTest::newRow("qml-bindingloop")
<< "file:///path/to/foo.qml:7:5: QML Row: Binding loop detected for property \"height\""
<< "/path/to/foo.qml"
<< 6 << 4 << FilteredItem::ErrorItem;
// END: Qt
}
void TestFilteringStrategy::testNativeAppErrorFilterStrategy()
{
QFETCH(QString, line);
QFETCH(QString, file);
QFETCH(int, lineNo);
QFETCH(int, column);
QFETCH(FilteredItem::FilteredOutputItemType, itemtype);
NativeAppErrorFilterStrategy testee;
FilteredItem item = testee.errorInLine(line);
QCOMPARE(item.url.path(), file);
QCOMPARE(item.lineNo , lineNo);
QCOMPARE(item.columnNo , column);
QCOMPARE(item.type , itemtype);
}
void TestFilteringStrategy::testStaticAnalysisFilterStrategy_data()
{
QTest::addColumn("line");
QTest::addColumn("expectedError");
QTest::addColumn("expectedAction");
QTest::addColumn("pathType");
QTest::newRow("cppcheck-info-line")
<< buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("cppcheck-error-line", pathType)
<< buildCppCheckErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;;
QTest::newRowForPathType("krazy2-error-line", pathType)
<< buildKrazyErrorLine(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("krazy2-error-line-two-colons", pathType)
<< buildKrazyErrorLine2(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("krazy2-error-line-error-description", pathType)
<< buildKrazyErrorLine3(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("krazy2-error-line-wo-line-info", pathType)
<< buildKrazyErrorLineNoLineInfo(pathType) << FilteredItem::ErrorItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-line", pathType)
<< buildCompilerLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
QTest::newRowForPathType("compiler-error-line", pathType)
<< buildCompilerErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
}
QTest::newRow("compiler-action-line")
<< "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem << UnixFilePathNoSpaces;
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
QTest::newRowForPathType("python-error-line", pathType)
<< buildPythonErrorLine(pathType) << FilteredItem::InvalidItem << FilteredItem::InvalidItem << pathType;
}
}
void TestFilteringStrategy::testStaticAnalysisFilterStrategy()
{
QFETCH(QString, line);
QFETCH(FilteredItem::FilteredOutputItemType, expectedError);
QFETCH(FilteredItem::FilteredOutputItemType, expectedAction);
QFETCH(TestPathType, pathType);
// Test that url's are extracted correctly as well
QString referencePath = projectPath(pathType) + "main.cpp";
StaticAnalysisFilterStrategy testee;
FilteredItem item1 = testee.errorInLine(line);
QString extractedPath = item1.url.toLocalFile();
QVERIFY((item1.type != FilteredItem::ErrorItem) || ( extractedPath == referencePath));
QCOMPARE(item1.type, expectedError);
item1 = testee.actionInLine(line);
QCOMPARE(item1.type, expectedAction);
}
void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction_data()
{
QTest::addColumn("line");
QTest::addColumn("expectedLastDir");
QTest::addColumn("pathType");
for (TestPathType pathType : {UnixFilePathNoSpaces, UnixFilePathWithSpaces}) {
const QString basepath = projectPath(pathType);
QTest::newRowForPathType("cmake-line1", pathType)
<< "[ 25%] Building CXX object path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ) << pathType;
QTest::newRowForPathType("cmake-line2", pathType)
<< "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two") << pathType;
QTest::newRowForPathType("cmake-line3", pathType)
<< "[ 26%] Building CXX object path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three") << pathType;
QTest::newRowForPathType("cmake-line4", pathType)
<< "[ 26%] Building CXX object path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four") << pathType;
QTest::newRowForPathType("cmake-line5", pathType)
<< "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two") << pathType;
QTest::newRowForPathType("cd-line6", pathType)
<< QString("make[4]: Entering directory '" + basepath + "/path/to/one/'") << QString( basepath + "/path/to/one") << pathType;
QTest::newRowForPathType("waf-cd", pathType)
<< QString("Waf: Entering directory `" + basepath + "/path/to/two/'") << QString( basepath + "/path/to/two") << pathType;
QTest::newRowForPathType("cmake-line7", pathType)
<< QStringLiteral("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath) << pathType;
QTest::newRowForPathType("cmake-cd-line8", pathType)
<< QString("> /usr/bin/cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug " + basepath) << QString( basepath ) << pathType;
}
}
void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction()
{
QFETCH(QString, line);
QFETCH(QString, expectedLastDir);
QFETCH(TestPathType, pathType);
QUrl projecturl = QUrl::fromLocalFile( projectPath(pathType) );
CompilerFilterStrategy testee(projecturl);
FilteredItem item1 = testee.actionInLine(line);
int last = testee.getCurrentDirs().size() - 1;
QCOMPARE(testee.getCurrentDirs().at(last), expectedLastDir);
}
void TestFilteringStrategy::benchMarkCompilerFilterAction()
{
QString projecturl = projectPath();
QStringList outputlines;
const int numLines(10000);
int j(0), k(0), l(0), m(0);
do {
++j; ++k; ++l;
QString tmp;
if(m % 2 == 0) {
tmp = QStringLiteral( "[ 26%] Building CXX object /this/is/the/path/to/the/files/%1/%2/%3/CMakeFiles/file.o").arg( j ).arg( k ).arg( l );
} else {
tmp = QString( "make[4]: Entering directory '" + projecturl + "/this/is/the/path/to/the/files/%1/%2/%3/").arg( j ).arg( k ).arg( l );
}
outputlines << tmp;
if(j % 6 == 0) {
j = 0; ++m;
}
if(k % 9 == 0) {
k = 0; ++m;
}
if(l % 13 == 0) {
l = 0; ++m;
}
}
while(outputlines.size() < numLines ); // gives us numLines (-ish)
QElapsedTimer totalTime;
totalTime.start();
static CompilerFilterStrategy testee(QUrl::fromLocalFile(projecturl));
FilteredItem item1(QStringLiteral("dummyline"), FilteredItem::InvalidItem);
QBENCHMARK {
for(int i = 0; i < outputlines.size(); ++i) {
item1 = testee.actionInLine(outputlines.at(i));
}
}
const qint64 elapsed = totalTime.elapsed();
qDebug() << "ms elapsed to add directories: " << elapsed;
qDebug() << "total number of directories: " << outputlines.count();
const double avgDirectoryInsertion = double(elapsed) / outputlines.count();
qDebug() << "average ms spend pr. dir: " << avgDirectoryInsertion;
QVERIFY(avgDirectoryInsertion < 2);
}
void TestFilteringStrategy::testExtractionOfLineAndColumn_data()
{
QTest::addColumn("line");
QTest::addColumn("file");
QTest::addColumn("lineNr");
QTest::addColumn("column");
QTest::addColumn("itemtype");
#ifdef Q_OS_WIN
QTest::newRow("msvc-compiler-error-line")
<< "Z:\\kderoot\\download\\git\\kcoreaddons\\src\\lib\\jobs\\kjob.cpp(3): error C2065: 'dadsads': undeclared identifier"
<< "Z:/kderoot/download/git/kcoreaddons/src/lib/jobs/kjob.cpp" << 2 << 0 << FilteredItem::ErrorItem;
QTest::newRow("msvc-compiler-warning-line")
<< "c:\\program files\\microsoft visual studio 10.0\\vc\\include\\crtdefs.h(527): warning C4229: anachronism used : modifiers on data are ignored"
<< "c:/program files/microsoft visual studio 10.0/vc/include/crtdefs.h" << 526 << 0 << FilteredItem::WarningItem;
#else
QTest::newRow("gcc-with-col")
<< "/path/to/file.cpp:123:45: fatal error: ..."
<< "/path/to/file.cpp" << 122 << 44 << FilteredItem::ErrorItem;
QTest::newRow("gcc-no-col")
<< "/path/to/file.cpp:123: error ..."
<< "/path/to/file.cpp" << 122 << 0 << FilteredItem::ErrorItem;
QTest::newRow("gcc-app-gives-invalid-column")
<< "/path/to/file.h:60:0:\
warning: \"SOME_MACRO\" redefined" << "/path/to/file.h" << 59 << 0 << FilteredItem::WarningItem;
QTest::newRow("fortcom")
<< "fortcom: Error: Ogive8.f90, line 123: ..."
<< QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem;
QTest::newRow("fortcomError")
<< "fortcom: Error: ./Ogive8.f90, line 123: ..."
<< QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem;
QTest::newRow("fortcomWarning")
<< "fortcom: Warning: /path/Ogive8.f90, line 123: ..."
<< "/path/Ogive8.f90" << 122 << 0 << FilteredItem::WarningItem;
QTest::newRow("fortcomInfo")
<< "fortcom: Info: Ogive8.f90, line 123: ..."
<< QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::InformationItem;
QTest::newRow("libtool")
<< "libtool: link: warning: ..."
<< "" << -1 << 0 << FilteredItem::WarningItem;
QTest::newRow("gfortranError1")
<< "/path/to/file.f90:123.456:Error: ...."
<< "/path/to/file.f90" << 122 << 455 << FilteredItem::ErrorItem;
QTest::newRow("gfortranError2")
<< "/path/flib.f90:3567.22:"
<< "/path/flib.f90" << 3566 << 21 << FilteredItem::ErrorItem;
QTest::newRow("ant-javac-Warning")
<< " [javac] /path/class.java:383: warning: [deprecation] ..."
<< "/path/class.java" << 382 << 0 << FilteredItem::WarningItem;
QTest::newRow("ant-javac-Error")
<< " [javac] /path/class.java:447: error: cannot find symbol"
<< "/path/class.java" << 446 << 0 << FilteredItem::ErrorItem;
+ QTest::newRow("cmake-error")
+ << "CMake Error at somesubdir/CMakeLists.txt:214:"
+ << "/some/path/to/a/somesubdir/CMakeLists.txt" << 213 << 0 << FilteredItem::ErrorItem;
#endif
}
void TestFilteringStrategy::testExtractionOfLineAndColumn()
{
QFETCH(QString, line);
QFETCH(QString, file);
QFETCH(int, lineNr);
QFETCH(int, column);
QFETCH(FilteredItem::FilteredOutputItemType, itemtype);
QUrl projecturl = QUrl::fromLocalFile( projectPath() );
CompilerFilterStrategy testee(projecturl);
FilteredItem item1 = testee.errorInLine(line);
QCOMPARE(item1.type , itemtype);
QCOMPARE(KDevelop::toUrlOrLocalFile(item1.url), file);
QCOMPARE(item1.lineNo , lineNr);
QCOMPARE(item1.columnNo , column);
}