diff --git a/outputview/filtereditem.cpp b/outputview/filtereditem.cpp index a8c57b3ee0..bbd0b440b5 100644 --- a/outputview/filtereditem.cpp +++ b/outputview/filtereditem.cpp @@ -1,49 +1,47 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * 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 Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "filtereditem.h" namespace KDevelop { FilteredItem::FilteredItem( const QString& line ) : originalLine( line ) , type( InvalidItem ) -, shortenedText( line ) , isActivatable(false) , lineNo(-1) , columnNo(-1) { } FilteredItem::FilteredItem( const QString& line, FilteredOutputItemType type ) : originalLine( line ) , type( type ) -, shortenedText( line ) , isActivatable(false) , lineNo(-1) , columnNo(-1) { } } // namespace KDevelop diff --git a/outputview/filtereditem.h b/outputview/filtereditem.h index e8ca6b2b12..b567671cca 100644 --- a/outputview/filtereditem.h +++ b/outputview/filtereditem.h @@ -1,74 +1,73 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * 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 Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_FILTEREDITEM_H #define KDEVPLATFORM_FILTEREDITEM_H #include #include #include #include namespace KDevelop { /** * Holds all metadata of a given compiler/script/whatever output line. E.g. if it is an error or * a warning, if it is clickable or not, line number, column number, etc. * Note that line and column number are stored as starting with with 0 (Your compiler/whatever tool * may count from 1 ). **/ struct KDEVPLATFORMOUTPUTVIEW_EXPORT FilteredItem { enum FilteredOutputItemType { InvalidItem = 0, ErrorItem = 1, WarningItem = 2, ActionItem = 3, CustomItem = 4, StandardItem = 5, InformationItem = 6 }; explicit FilteredItem( const QString& line = QString() ); FilteredItem( const QString& line, FilteredOutputItemType type ); QString originalLine; FilteredOutputItemType type; - QString shortenedText; bool isActivatable; QUrl url; /// lineNo starts with 0 int lineNo; /// columnNo starts with 0 int columnNo; }; } // namespace KDevelop Q_DECLARE_METATYPE( KDevelop::FilteredItem::FilteredOutputItemType ) Q_DECLARE_TYPEINFO( KDevelop::FilteredItem, Q_MOVABLE_TYPE ); #endif // KDEVPLATFORM_FILTEREDITEM_H diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index 2f7c3bb35d..52d2551d1c 100644 --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -1,451 +1,430 @@ /* 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 using namespace KDevelop; namespace { 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() ) { item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup )); item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } QString txt = match.captured(curErrFilter.textGroup); 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 --- CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir) : m_buildDir(buildDir) { } Path CompilerFilterStrategy::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(currentPath.toLocalFile()).exists() ); return currentPath; } else { currentPath = Path(filename); } return currentPath; } bool CompilerFilterStrategy::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) { return true; } return false; } void CompilerFilterStrategy::putDirAtEnd(const Path& pathToInsert) { auto 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; } } QVector CompilerFilterStrategy::getCurrentDirs() { QVector ret; ret.reserve(m_currentDirs.size()); for (const auto& path : 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( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), 1, 2, + 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( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), 1, 2, - QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")), + ActionFormat( 2, QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")), //libtool linking - ActionFormat( ki18nc("Linking object files into a library or executable: %1 target, %2 linker", "linking %1 (%2)"), - QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ), + ActionFormat( QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ), //unsermake - ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), 1, 1, - QStringLiteral("^compiling (.*)") ), - ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), 1, 2, - QStringLiteral("^generating (.*)") ), - ActionFormat( ki18nc("Linking object files into a library or executable: %1 file, %2 linker", "linking %1 (%2)"), 1, 2, - QStringLiteral("(gcc|cc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)")), - ActionFormat( ki18nc("Linking object files into a library or executable: %1 file, %2 compiler", "linking %1 (%2)"), 1, 2, - QStringLiteral("^linking (.*)") ), + 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( ki18nc("finished building a target: %1 target name", "built %1"), -1, 1, - QStringLiteral("\\[.+%\\] Built target (.*)") ), - ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), QStringLiteral("cmake"), + ActionFormat( 1, QStringLiteral("\\[.+%\\] Built target (.*)") ), + ActionFormat( QStringLiteral("cmake"), QStringLiteral("\\[.+%\\] Building .* object (.*)"), 1 ), - ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), -1, 1, - QStringLiteral("\\[.+%\\] Generating (.*)") ), - ActionFormat( ki18nc("Linking object files into a library or executable: %1 target", "linking %1"), -1, 1, - QStringLiteral("^Linking (.*)") ), - ActionFormat( ki18nc("configuring a project: %1 tool", "configuring (%1)"), QStringLiteral("cmake"), + ActionFormat( 1, QStringLiteral("\\[.+%\\] Generating (.*)") ), + ActionFormat( 1, QStringLiteral("^Linking (.*)") ), + ActionFormat( QStringLiteral("cmake"), QStringLiteral("(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)"), -1 ), - ActionFormat( ki18nc("installing a file: %1 file", "installing %1"), -1, 1, - QStringLiteral("-- Installing (.*)") ), + ActionFormat( 1, QStringLiteral("-- Installing (.*)") ), //libtool install - ActionFormat( ki18nc("creating a folder: %1 path", "creating %1"), {}, + ActionFormat( {}, QStringLiteral("/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)"), 1 ), - ActionFormat( ki18nc("installing a file: %1 file", "installing %1"), {}, + ActionFormat( {}, QStringLiteral("/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)"), 1 ), //dcop - ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), QStringLiteral("dcopidl"), + ActionFormat( QStringLiteral("dcopidl"), QStringLiteral("dcopidl .* > ([^\\s;]+)"), 1 ), - ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), QStringLiteral("dcopidl2cpp"), + ActionFormat( QStringLiteral("dcopidl2cpp"), QStringLiteral("dcopidl2cpp (?:\\S* )*([^\\s;]+)"), 1 ), // match against Entering directory to update current build dir - ActionFormat( ki18nc("change directory: %1: path", "entering %1 (%2)"), QStringLiteral("cd"), + ActionFormat( QStringLiteral("cd"), QStringLiteral("make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'"), 2), // waf and scons use the same basic convention as make - ActionFormat( ki18nc("change directory: %1: path", "entering %1 (%2)"), QStringLiteral("cd"), + 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; - KLocalizedString shortened = curActFilter.label; - if (curActFilter.fileGroup != -1) { - shortened = shortened.subs(match.captured(curActFilter.fileGroup)); - } - if (curActFilter.toolGroup != -1 ) { - shortened = shortened.subs(match.captured(curActFilter.toolGroup)); - } else if (!curActFilter.tool.isEmpty()) { - shortened = shortened.subs(curActFilter.tool); - } - - item.shortenedText = shortened.toString(); if( curActFilter.tool == "cd" ) { const Path path(match.captured(curActFilter.fileGroup)); m_currentDirs.push_back( path ); m_positionInCurrentDirs.insert( path , 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 == "cmake" && line.contains("Building")) { const auto objectFile = match.captured(curActFilter.fileGroup); const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0); putDirAtEnd(Path(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 ), // 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 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 == "cmake" ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this if( m_currentDirs.empty() ) { putDirAtEnd( m_buildDir.parent() ); } } item.url = pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); } item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } 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(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 ErrorFormat(QStringLiteral("(file:\\/\\/(?:.+)):([1-9][0-9]*):([1-9][0-9]*): (.+) (?:is not a type|is ambiguous\\.|is instantiated recursively)"), 1, 2, -1, 3) // 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/outputview/outputformats.cpp b/outputview/outputformats.cpp index dec179b4a2..cc2a8d840d 100644 --- a/outputview/outputformats.cpp +++ b/outputview/outputformats.cpp @@ -1,55 +1,51 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann * * Copyright 2007 Dukju Ahn * * 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. * * * ***************************************************************************/ #include "outputformats.h" #include namespace KDevelop { ErrorFormat::ErrorFormat( const QString& regExp, int file, int line, int text, int column ) : expression( regExp ) , fileGroup( file ) , lineGroup( line ) , columnGroup( column ) , textGroup( text ) {} ErrorFormat::ErrorFormat( const QString& regExp, int file, int line, int text, const QString& comp, int column ) : expression( regExp ) , fileGroup( file ) , lineGroup( line ) , columnGroup( column ) , textGroup( text ) , compiler( comp ) {} -ActionFormat::ActionFormat( const KLocalizedString& label, const QString& _tool, const QString& regExp, int file ) - : label( label ) - , expression( regExp ) +ActionFormat::ActionFormat(const QString& _tool, const QString& regExp, int file ) + : expression( regExp ) , tool( _tool ) - , toolGroup(-1) , fileGroup( file ) { } -ActionFormat::ActionFormat( const KLocalizedString& label, int tool, int file, const QString& regExp) - : label( label ) - , expression( regExp ) - , toolGroup( tool ) +ActionFormat::ActionFormat(int file, const QString& regExp) + : expression( regExp ) , fileGroup( file ) { } } diff --git a/outputview/outputformats.h b/outputview/outputformats.h index 6611874b03..b28f3bf1e7 100644 --- a/outputview/outputformats.h +++ b/outputview/outputformats.h @@ -1,50 +1,48 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann * * Copyright 2007 Dukju Ahn * * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_OUTPUTFORMATS_H #define KDEVPLATFORM_OUTPUTFORMATS_H #include #include #include namespace KDevelop { struct ActionFormat { ActionFormat() = default; - ActionFormat( const KLocalizedString& label, const QString& _tool, const QString& regExp, int file ); - ActionFormat( const KLocalizedString& label, int tool, int file, const QString& regExp ); - KLocalizedString label; + ActionFormat( const QString& _tool, const QString& regExp, int file ); + ActionFormat( int file, const QString& regExp ); QRegularExpression expression; QString tool; - int toolGroup; int fileGroup; }; struct ErrorFormat { ErrorFormat() = default; ErrorFormat( const QString& regExp, int file, int line, int text, int column=-1 ); ErrorFormat( const QString& regExp, int file, int line, int text, const QString& comp, int column=-1 ); QRegularExpression expression; int fileGroup; int lineGroup, columnGroup; int textGroup; QString compiler; }; } #endif diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp index 046626683e..201a6eb0d9 100644 --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -1,421 +1,421 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * 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 Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() : QObject(0) , m_filter(new NoFilterStrategy) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ParseWorker::process); } public slots: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } void flushBuffers() { m_timer->stop(); process(); emit allDone(); } signals: void parsedBatch(const QVector& filteredItems); void allDone(); private slots: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); // apply pre-filtering functions std::transform(m_cachedLines.constBegin(), m_cachedLines.constEnd(), m_cachedLines.begin(), &KDevelop::stripAnsiSequences); // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName("OutputFilterThread"); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; Q_GLOBAL_STATIC(ParsingThread, s_parsingThread); struct OutputModelPrivate { OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next QUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); foreach( const FilteredItem& item, items ) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const QUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const QUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() { delete d; } QVariant OutputModel::data(const QModelIndex& idx , int role ) const { if( d->isValidIndex(idx, rowCount()) ) { switch( role ) { case Qt::DisplayRole: - return d->m_filteredItems.at( idx.row() ).shortenedText; + return d->m_filteredItems.at( idx.row() ).originalLine; break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); break; case Qt::FontRole: return QFontDatabase::systemFont(QFontDatabase::FixedFont); break; default: break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } qCDebug(OUTPUTVIEW) << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { qCDebug(OUTPUTVIEW) << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); QUrl url = item.url; if (item.url.isEmpty()) { qWarning() << "trying to open empty url"; return; } if(url.isRelative()) { url = d->m_buildDir.resolved(url); } Q_ASSERT(!url.isRelative()); docCtrl->openDocument( url, range ); } else { qCDebug(OUTPUTVIEW) << "not an activateable item"; } } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { qCDebug(OUTPUTVIEW) << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { qCDebug(OUTPUTVIEW) << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { // TODO: Turn into factory, decouple from OutputModel IFilterStrategy* filter = 0; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; default: // assert(false); filter = new NoFilterStrategy; break; } Q_ASSERT(filter); QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& l ) { appendLines( QStringList() << l ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/outputview/tests/test_filteringstrategy.cpp b/outputview/tests/test_filteringstrategy.cpp index 9d02aba50b..63409ac349 100644 --- a/outputview/tests/test_filteringstrategy.cpp +++ b/outputview/tests/test_filteringstrategy.cpp @@ -1,507 +1,475 @@ /* 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"); } } void TestFilteringStrategy::testNoFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expected"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << buildCompilerActionLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << 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::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line2") << buildInfileIncludedFromFirstLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line3") << buildInfileIncludedFromSecondLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-line1") << "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline1") << "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline2") << buildCmakeConfigureMultiLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-warning-line") << "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc-error") << "AUTOMOC: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc4-error") << "automoc4: The file \"/foo/bar.cpp\" includes the moc file \"bar1.moc\"" << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-autogen-error") << "AUTOGEN: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("linker-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem; QTest::newRow("linker-error-line") << buildLinkerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterStrategy() { 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::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::testCompilerFilterStrategyShortenedText_data() -{ - QTest::addColumn("line"); - QTest::addColumn("expectedShortenedText"); - - QTest::newRow("c++-compile") - << "g++ -c main.cpp -o main.o" << "compiling main.cpp (g++)"; - QTest::newRow("clang++-link") - << "clang++ -c main.cpp -o main.o" << "compiling main.cpp (clang++)"; - // see bug: https://bugs.kde.org/show_bug.cgi?id=240017 - QTest::newRow("mpicc-link") - << "/usr/bin/mpicc -c main.cpp -o main.o" << "compiling main.cpp (mpicc)"; - - QTest::newRow("c++-link") - << "/usr/bin/g++ main.cpp -o main" << "linking main (g++)"; - QTest::newRow("clang++-link") - << "/usr/bin/clang++ main.cpp -o a.out" << "linking a.out (clang++)"; - QTest::newRow("mpicc-link") - << "mpicc main.cpp -o main" << "linking main (mpicc)"; -} - -void TestFilteringStrategy::testCompilerFilterStrategyShortenedText() -{ - QFETCH(QString, line); - QFETCH(QString, expectedShortenedText); - - QUrl projecturl = QUrl::fromLocalFile( projectPath() ); - CompilerFilterStrategy testee(projecturl); - FilteredItem item = testee.actionInLine(line); - QCOMPARE(item.shortenedText, expectedShortenedText); -} - void TestFilteringStrategy::testScriptErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << 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; // 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::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line") << buildKrazyErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-two-colons") << buildKrazyErrorLine2() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-error-description") << buildKrazyErrorLine3() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-wo-line-info") << buildKrazyErrorLineNoLineInfo() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testStaticAnalysisFilterStrategy() { // Test that url's are extracted correctly as well QString referencePath = projectPath() + "main.cpp"; QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); 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"); QString basepath = projectPath(); QTest::newRow("cmake-line1") << "[ 25%] Building CXX object path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ); QTest::newRow("cmake-line2") << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line3") << "[ 26%] Building CXX object path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three"); QTest::newRow("cmake-line4") << "[ 26%] Building CXX object path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four"); QTest::newRow("cmake-line5") << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cd-line6") << QString("make[4]: Entering directory '" + basepath + "/path/to/one/'") << QString( basepath + "/path/to/one"); QTest::newRow("waf-cd") << QString("Waf: Entering directory `" + basepath + "/path/to/two/'") << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line7") << QStringLiteral("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction() { QFETCH(QString, line); QFETCH(QString, expectedLastDir); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); static CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.actionInLine(line); QCOMPARE(testee.getCurrentDirs().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("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("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; #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); } diff --git a/outputview/tests/test_filteringstrategy.h b/outputview/tests/test_filteringstrategy.h index f3d566f7e4..5e47424f68 100644 --- a/outputview/tests/test_filteringstrategy.h +++ b/outputview/tests/test_filteringstrategy.h @@ -1,54 +1,52 @@ /* 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 . */ #ifndef KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H #define KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H #include namespace KDevelop { class TestFilteringStrategy : public QObject { Q_OBJECT private slots: void testNoFilterStrategy_data(); void testNoFilterStrategy(); void testCompilerFilterStrategy_data(); void testCompilerFilterStrategy(); void testCompilerFilterstrategyMultipleKeywords_data(); void testCompilerFilterstrategyMultipleKeywords(); - void testCompilerFilterStrategyShortenedText(); - void testCompilerFilterStrategyShortenedText_data(); void testCompilerFilterstrategyUrlFromAction_data(); void testCompilerFilterstrategyUrlFromAction(); void testScriptErrorFilterStrategy_data(); void testScriptErrorFilterStrategy(); void testNativeAppErrorFilterStrategy_data(); void testNativeAppErrorFilterStrategy(); void testStaticAnalysisFilterStrategy_data(); void testStaticAnalysisFilterStrategy(); void testExtractionOfLineAndColumn_data(); void testExtractionOfLineAndColumn(); void benchMarkCompilerFilterAction(); }; } #endif // KDEVPLATFORM_TEST_FILTERINGSTRATEGY_H