diff --git a/interfaces/iproblem.h b/interfaces/iproblem.h index b97aeda6d7..a3f2b93f27 100644 --- a/interfaces/iproblem.h +++ b/interfaces/iproblem.h @@ -1,120 +1,123 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 IPROBLEM_H #define IPROBLEM_H #include #include #include #include namespace KDevelop { class IAssistant; /// Interface for the Problem classes class IProblem : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; /// The source of the problem. That is which tool / which part found this problem. enum Source { Unknown, Disk, Preprocessor, Lexer, Parser, DUChainBuilder, SemanticAnalysis, ToDo, Plugin /// The source is a problem checker plugin }; /// Severity of the problem. That is, how serious is the found problem. enum Severity { - Error, - Warning, - Hint + NoSeverity = 0, + Error = 1, + Warning = 2, + Hint = 4 }; - + Q_DECLARE_FLAGS(Severities, Severity) IProblem(){} virtual ~IProblem(){} /// Returns the source of the problem virtual Source source() const = 0; /// Sets the source of the problem virtual void setSource(Source source) = 0; /// Returns a string containing the source of the problem virtual QString sourceString() const = 0; /// Returns the location of the problem (path, line, column) virtual KDevelop::DocumentRange finalLocation() const = 0; /// Sets the location of the problem (path, line, column) virtual void setFinalLocation(const KDevelop::DocumentRange& location) = 0; /// Returns the short description of the problem. virtual QString description() const = 0; /// Sets the short description of the problem virtual void setDescription(const QString& description) = 0; /// Returns the detailed explanation of the problem. virtual QString explanation() const = 0; /// Sets the detailed explanation of the problem virtual void setExplanation(const QString& explanation) = 0; /// Returns the severity of the problem virtual Severity severity() const = 0; /// Sets the severity of the problem virtual void setSeverity(Severity severity) = 0; /// Returns a string containing the severity of the problem virtual QString severityString() const = 0; /// Returns the diagnostics of the problem. virtual QVector diagnostics() const = 0; /// Sets the diagnostics of the problem virtual void setDiagnostics(const QVector &diagnostics) = 0; /// Adds a diagnostic line to the problem virtual void addDiagnostic(const Ptr &diagnostic) = 0; /// Clears all diagnostics virtual void clearDiagnostics() = 0; /// Returns a solution assistant for the problem, if applicable that is. virtual QExplicitlySharedDataPointer solutionAssistant() const = 0; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(IProblem::Severities) + } Q_DECLARE_METATYPE(KDevelop::IProblem::Ptr); #endif diff --git a/plugins/problemreporter/problemreportermodel.cpp b/plugins/problemreporter/problemreportermodel.cpp index 517804cd80..c9fea11080 100644 --- a/plugins/problemreporter/problemreportermodel.cpp +++ b/plugins/problemreporter/problemreportermodel.cpp @@ -1,192 +1,192 @@ /* * Copyright 2007 Hamish Rodda * Copyright 2015 Laszlo Kis-Adam * * 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 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 "problemreportermodel.h" #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include using namespace KDevelop; const int ProblemReporterModel::MinTimeout = 1000; const int ProblemReporterModel::MaxTimeout = 5000; ProblemReporterModel::ProblemReporterModel(QObject* parent) - : ProblemModel(parent, new ProblemStore()) + : ProblemModel(parent, new FilteredProblemStore()) , m_showImports(false) { setFeatures(CanDoFullUpdate | CanShowImports | ScopeFilter | SeverityFilter); m_minTimer = new QTimer(this); m_minTimer->setInterval(MinTimeout); m_minTimer->setSingleShot(true); connect(m_minTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); m_maxTimer = new QTimer(this); m_maxTimer->setInterval(MaxTimeout); m_maxTimer->setSingleShot(true); connect(m_maxTimer, &QTimer::timeout, this, &ProblemReporterModel::timerExpired); - connect(store(), &ProblemStore::changed, this, &ProblemReporterModel::onProblemsChanged); + connect(store(), &FilteredProblemStore::changed, this, &ProblemReporterModel::onProblemsChanged); } ProblemReporterModel::~ProblemReporterModel() { } QVector ProblemReporterModel::problems(const KDevelop::IndexedString& url, bool showImports) const { QVector result; QSet visitedContexts; KDevelop::DUChainReadLocker lock; problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); return result; } QVector ProblemReporterModel::problems(const QSet& urls, bool showImports) const { QVector result; QSet visitedContexts; KDevelop::DUChainReadLocker lock; foreach (const KDevelop::IndexedString& url, urls) { problemsInternal(KDevelop::DUChain::self()->chainForDocument(url), showImports, visitedContexts, result); } return result; } void ProblemReporterModel::forceFullUpdate() { Q_ASSERT(thread() == QThread::currentThread()); QSet documents = store()->documents()->get(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); foreach (const KDevelop::IndexedString& document, documents) { if (document.isEmpty()) continue; KDevelop::TopDUContext::Features updateType = KDevelop::TopDUContext::ForceUpdate; if (documents.size() == 1) updateType = KDevelop::TopDUContext::ForceUpdateRecursive; KDevelop::DUChain::self()->updateContextForUrl( document, (KDevelop::TopDUContext::Features)(updateType | KDevelop::TopDUContext::VisibleDeclarationsAndContexts)); } } void ProblemReporterModel::problemsInternal(KDevelop::TopDUContext* context, bool showImports, QSet& visitedContexts, QVector& result) const { if (!context || visitedContexts.contains(context)) { return; } foreach (KDevelop::ProblemPointer p, context->problems()) { if (p && p->severity() <= store()->severity()) { result.append(p); } } visitedContexts.insert(context); if (showImports) { bool isProxy = context->parsingEnvironmentFile() && context->parsingEnvironmentFile()->isProxyContext(); foreach (const KDevelop::DUContext::Import& ctx, context->importedParentContexts()) { if (!ctx.indexedContext().indexedTopContext().isLoaded()) continue; KDevelop::TopDUContext* topCtx = dynamic_cast(ctx.context(0)); if (topCtx) { /// If we are starting at a proxy-context, only recurse into other proxy-contexts, because those contain the problems. if (!isProxy || (topCtx->parsingEnvironmentFile() && topCtx->parsingEnvironmentFile()->isProxyContext())) problemsInternal(topCtx, showImports, visitedContexts, result); } } } } void ProblemReporterModel::onProblemsChanged() { rebuildProblemList(); } void ProblemReporterModel::timerExpired() { m_minTimer->stop(); m_maxTimer->stop(); rebuildProblemList(); } void ProblemReporterModel::setCurrentDocument(KDevelop::IDocument* doc) { Q_ASSERT(thread() == QThread::currentThread()); beginResetModel(); QUrl currentDocument = doc->url(); /// Will trigger signal changed() if problems change store()->setCurrentDocument(KDevelop::IndexedString(currentDocument)); endResetModel(); } void ProblemReporterModel::problemsUpdated(const KDevelop::IndexedString& url) { Q_ASSERT(thread() == QThread::currentThread()); if (store()->documents()->get().contains(url)) { /// m_minTimer will expire in MinTimeout unless some other parsing job finishes in this period. m_minTimer->start(); /// m_maxTimer will expire unconditionally in MaxTimeout if (!m_maxTimer->isActive()) { m_maxTimer->start(); } } } void ProblemReporterModel::setShowImports(bool showImports) { if (m_showImports != showImports) { Q_ASSERT(thread() == QThread::currentThread()); m_showImports = showImports; rebuildProblemList(); } } void ProblemReporterModel::rebuildProblemList() { QVector problems; /// No locking here, because it may be called from an already locked context beginResetModel(); problems = this->problems(store()->documents()->get(), m_showImports); store()->setProblems(problems); endResetModel(); } diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index d69f479c98..d56e6c8346 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,388 +1,393 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * Copyright 2006 Adam Treat * * 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 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 "problemtreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include #include #include //#include "modeltest.h" using namespace KDevelop; namespace KDevelop { class ProblemTreeViewItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ProblemTreeViewItemDelegate(QObject* parent = nullptr); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } ProblemTreeViewItemDelegate::ProblemTreeViewItemDelegate(QObject* parent) : QItemDelegate(parent) { } void ProblemTreeViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == ProblemModel::File ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } ProblemTreeView::ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel) : QTreeView(parent) , m_proxy(new QSortFilterProxyModel(this)) + , errorSeverityAction(nullptr) + , warningSeverityAction(nullptr) + , hintSeverityAction(nullptr) { - setObjectName(QStringLiteral("Problem Reporter Tree")); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate(this)); setSelectionBehavior(QAbstractItemView::SelectRows); m_proxy->setSortRole(ProblemModel::SeverityRole); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); ProblemModel* problemModel = dynamic_cast(itemModel); Q_ASSERT(problemModel); setModel(problemModel); header()->setStretchLastSection(false); if (problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)) { QAction* fullUpdateAction = new QAction(this); fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); fullUpdateAction->setText(i18n("Force Full Update")); fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(fullUpdateAction, &QAction::triggered, model(), &ProblemModel::forceFullUpdate); addAction(fullUpdateAction); } if (problemModel->features().testFlag(ProblemModel::CanShowImports)) { QAction* showImportsAction = new QAction(this); addAction(showImportsAction); showImportsAction->setCheckable(true); showImportsAction->setChecked(false); showImportsAction->setText(i18n("Show Imports")); showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); this->model()->setShowImports(false); connect(showImportsAction, &QAction::triggered, model(), &ProblemModel::setShowImports); } if (problemModel->features().testFlag(ProblemModel::ScopeFilter)) { KActionMenu* scopeMenu = new KActionMenu(this); scopeMenu->setDelayed(false); scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); QAction* currentDocumentAction = new QAction(this); currentDocumentAction->setText(i18n("Current Document")); currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { QAction* showAllAction = new QAction(this); showAllAction->setText(i18n("Show All")); showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(showAllAction); } foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); scopeMenu->addAction(action); } addAction(scopeMenu); setScope(CurrentDocument); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { actions.last()->setChecked(true); model()->setScope(BypassScopeFilter); } else { currentDocumentAction->setChecked(true); model()->setScope(CurrentDocument); } QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, &ProblemTreeView::setScope); } if (problemModel->features().testFlag(ProblemModel::SeverityFilter)) { QActionGroup* severityActions = new QActionGroup(this); - auto errorSeverityAction = new QAction(this); - errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display only errors")); + + errorSeverityAction = new QAction(this); + errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); - auto warningSeverityAction = new QAction(this); - warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors and warnings")); + warningSeverityAction = new QAction(this); + warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); - auto hintSeverityAction = new QAction(this); - hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors, warnings and hints")); + hintSeverityAction = new QAction(this); + hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); QAction* severityActionArray[] = { errorSeverityAction, warningSeverityAction, hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } + severityActions->setExclusive(false); hintSeverityAction->setChecked(true); - model()->setSeverity(IProblem::Hint); - QSignalMapper* severityMapper = new QSignalMapper(this); - severityMapper->setMapping(errorSeverityAction, IProblem::Error); - severityMapper->setMapping(warningSeverityAction, IProblem::Warning); - severityMapper->setMapping(hintSeverityAction, IProblem::Hint); - connect(errorSeverityAction, &QAction::triggered, severityMapper, - static_cast(&QSignalMapper::map)); - connect(warningSeverityAction, &QAction::triggered, severityMapper, - static_cast(&QSignalMapper::map)); - connect(hintSeverityAction, &QAction::triggered, severityMapper, - static_cast(&QSignalMapper::map)); - connect(severityMapper, static_cast(&QSignalMapper::mapped), model(), - &ProblemModel::setSeverity); + warningSeverityAction->setChecked(true); + errorSeverityAction->setChecked(true); + + model()->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); + connect(errorSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); + connect(warningSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); + connect(hintSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); } if (problemModel->features().testFlag(ProblemModel::Grouping)) { KActionMenu* groupingMenu = new KActionMenu(i18n("Grouping"), this); groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); groupingMenu->addAction(action); } addAction(groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setGrouping); } connect(this, &ProblemTreeView::clicked, this, &ProblemTreeView::itemActivated); connect(model(), &QAbstractItemModel::rowsInserted, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::rowsRemoved, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::modelReset, this, &ProblemTreeView::changed); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::openDocumentForCurrentProblem() { itemActivated(currentIndex()); } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) return; url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } ICore::self()->documentController()->openDocument(url, start); } +void ProblemTreeView::handleSeverityActionToggled() +{ + model()->setSeverities( (errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | + (warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | + (hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); +} + void ProblemTreeView::setScope(int scope) { foreach (auto action, actions()) { if (action->objectName() == QLatin1String("scopeMenu")) { action->setText(i18n("Scope: %1", action->menu()->actions().at(scope)->text())); } } model()->setScope(scope); } void ProblemTreeView::resizeColumns() { for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } ProblemModel* ProblemTreeView::model() const { return static_cast(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); m_proxy->setSourceModel(model); QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (index.isValid()) { const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) { return; } QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if (!solution) { return; } QList actions; foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) { actions << action->toKAction(); } if (!actions.isEmpty()) { QString title = solution->title(); title = KDevelop::htmlToPlainText(title); title.replace(QLatin1String("'"), QLatin1String("\'")); QPointer m = new QMenu(this); m->addSection(title); m->addActions(actions); m->exec(event->globalPos()); delete m; } } } void ProblemTreeView::showEvent(QShowEvent* event) { Q_UNUSED(event) resizeColumns(); } #include "problemtreeview.moc" diff --git a/plugins/problemreporter/problemtreeview.h b/plugins/problemreporter/problemtreeview.h index 092ce88e17..7ff80ceb60 100644 --- a/plugins/problemreporter/problemtreeview.h +++ b/plugins/problemreporter/problemtreeview.h @@ -1,76 +1,80 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2007 Hamish Rodda * * 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 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_PLUGIN_PROBLEMWIDGET_H #define KDEVPLATFORM_PLUGIN_PROBLEMWIDGET_H #include namespace KDevelop { class TopDUContext; class IDocument; class ProblemModel; } class ProblemReporterPlugin; class QSortFilterProxyModel; class ProblemTreeView : public QTreeView { Q_OBJECT public: ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel); ~ProblemTreeView() override; KDevelop::ProblemModel* model() const; void setModel(QAbstractItemModel* model) override; void contextMenuEvent(QContextMenuEvent*) override; void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()) override; void reset() override; public slots: void openDocumentForCurrentProblem(); signals: // Emitted when the model's rows change (added/removed/reset) void changed(); protected: void showEvent(QShowEvent* event) override; private slots: void itemActivated(const QModelIndex& index); + void handleSeverityActionToggled(); void setScope(int scope); private: void resizeColumns(); ProblemReporterPlugin* m_plugin; QSortFilterProxyModel* m_proxy; + QAction* errorSeverityAction; + QAction* warningSeverityAction; + QAction* hintSeverityAction; }; #endif // kate: space-indent on; indent-width 2; tab-width: 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/standardoutputview/tests/test_standardoutputview.cpp b/plugins/standardoutputview/tests/test_standardoutputview.cpp index f06ff4b4f1..c10c80fadd 100644 --- a/plugins/standardoutputview/tests/test_standardoutputview.cpp +++ b/plugins/standardoutputview/tests/test_standardoutputview.cpp @@ -1,234 +1,234 @@ /* Copyright (C) 2011 Silvère Lestang 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_standardoutputview.h" #include "../outputwidget.h" #include "../toolviewdata.h" namespace KDevelop { class IUiController; } class QAbstractItemDelegate; class QStandardItemModel; QTEST_MAIN(StandardOutputViewTest) const QString StandardOutputViewTest::toolviewTitle = QStringLiteral("my_toolview"); void StandardOutputViewTest::initTestCase() { KDevelop::AutoTestShell::init(); m_testCore = new KDevelop::TestCore(); m_testCore->initialize(KDevelop::Core::Default); m_controller = m_testCore->uiControllerInternal(); QTest::qWait(500); // makes sure that everything is loaded (don't know if it's required) m_stdOutputView = 0; KDevelop::IPluginController* plugin_controller = m_testCore->pluginController(); QList plugins = plugin_controller->loadedPlugins(); // make sure KDevStandardOutputView is loaded KDevelop::IPlugin* plugin = plugin_controller->loadPlugin(QStringLiteral("KDevStandardOutputView")); QVERIFY(plugin); m_stdOutputView = dynamic_cast(plugin); QVERIFY(m_stdOutputView); } void StandardOutputViewTest::cleanupTestCase() { m_testCore->cleanup(); delete m_testCore; } OutputWidget* StandardOutputViewTest::toolviewPointer(QString toolviewTitle) { QList< Sublime::View* > views = m_controller->activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc) { if(doc->title() == toolviewTitle && view->hasWidget()) { return dynamic_cast(view->widget()); } } } return 0; } void StandardOutputViewTest::testRegisterAndRemoveToolView() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView); QVERIFY(toolviewPointer(toolviewTitle)); // re-registering should return the same tool view instead of creating a new one QCOMPARE(toolviewId, m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView)); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testActions() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QList actions = outputWidget->actions(); - QCOMPARE(actions.size(), 8); + QCOMPARE(actions.size(), 10); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); QList addedActions; addedActions.append(new QAction(QStringLiteral("Action1"), 0)); addedActions.append(new QAction(QStringLiteral("Action2"), 0)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction, addedActions); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); actions = outputWidget->actions(); - QCOMPARE(actions.size(), 13); + QCOMPARE(actions.size(), 15); QCOMPARE(actions[actions.size()-2]->text(), addedActions[0]->text()); QCOMPARE(actions[actions.size()-1]->text(), addedActions[1]->text()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testRegisterAndRemoveOutput() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); QCOMPARE(outputWidget->tabwidget->tabText(i), QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->tabwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::HistoryView, QIcon(), KDevelop::IOutputView::ShowItemsButton | KDevelop::IOutputView::AddFilterAction); outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); for(int i = 0; i < 5; i++) { outputId[i] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { QCOMPARE(outputWidget->data->outputdata.value(outputId[i])->title, QStringLiteral("output%1").arg(i)); } for(int i = 0; i < 5; i++) { m_stdOutputView->removeOutput(outputId[i]); QVERIFY(!outputWidget->data->outputdata.contains(outputId[i])); } QCOMPARE(outputWidget->stackwidget->count(), 0); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); } void StandardOutputViewTest::testSetModelAndDelegate() { toolviewId = m_stdOutputView->registerToolView(toolviewTitle, KDevelop::IOutputView::MultipleView, QIcon()); OutputWidget* outputWidget = toolviewPointer(toolviewTitle); QVERIFY(outputWidget); QAbstractItemModel* model = new QStandardItemModel; QPointer checkModel(model); QAbstractItemDelegate* delegate = new QItemDelegate; QPointer checkDelegate(delegate); outputId[0] = m_stdOutputView->registerOutputInToolView(toolviewId, QStringLiteral("output")); m_stdOutputView->setModel(outputId[0], model); m_stdOutputView->setDelegate(outputId[0], delegate); QCOMPARE(outputWidget->views.value(outputId[0])->model(), model); QCOMPARE(outputWidget->views.value(outputId[0])->itemDelegate(), delegate); QVERIFY(model->parent()); // they have a parent (the outputdata), so parent() != 0x0 QVERIFY(delegate->parent()); m_stdOutputView->removeToolView(toolviewId); QVERIFY(!toolviewPointer(toolviewTitle)); // view deleted, hence model + delegate deleted QVERIFY(!checkModel.data()); QVERIFY(!checkDelegate.data()); } void StandardOutputViewTest::testStandardToolViews() { QFETCH(KDevelop::IOutputView::StandardToolView, view); int id = m_stdOutputView->standardToolView(view); QVERIFY(id); QCOMPARE(id, m_stdOutputView->standardToolView(view)); } void StandardOutputViewTest::testStandardToolViews_data() { QTest::addColumn("view"); QTest::newRow("build") << KDevelop::IOutputView::BuildView; QTest::newRow("run") << KDevelop::IOutputView::RunView; QTest::newRow("debug") << KDevelop::IOutputView::DebugView; QTest::newRow("test") << KDevelop::IOutputView::TestView; QTest::newRow("vcs") << KDevelop::IOutputView::VcsView; } diff --git a/shell/filteredproblemstore.cpp b/shell/filteredproblemstore.cpp index 73e92e73af..cf5cbde2f7 100644 --- a/shell/filteredproblemstore.cpp +++ b/shell/filteredproblemstore.cpp @@ -1,327 +1,335 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 "filteredproblemstore.h" #include "problem.h" #include "watcheddocumentset.h" #include "problemstorenode.h" #include using namespace KDevelop; namespace { /// Adds diagnostics as sub-nodes void addDiagnostics(ProblemStoreNode *node, const QVector &diagnostics) { foreach (const IProblem::Ptr &ptr, diagnostics) { ProblemNode *child = new ProblemNode(node, ptr); node->addChild(child); addDiagnostics(child, ptr->diagnostics()); } } /** * @brief Base class for grouping strategy classes * * These classes build the problem tree based on the respective strategies */ class GroupingStrategy { public: GroupingStrategy( ProblemStoreNode *root ) : m_rootNode(root) , m_groupedRootNode(new ProblemStoreNode()) { } virtual ~GroupingStrategy(){ } /// Add a problem to the appropriate group virtual void addProblem(const IProblem::Ptr &problem) = 0; /// Find the specified noe const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const { if (parent == nullptr) return m_groupedRootNode->child(row); else return parent->child(row); } /// Returns the number of children nodes int count(ProblemStoreNode *parent = nullptr) { if (parent == nullptr) return m_groupedRootNode->count(); else return parent->count(); } /// Clears the problems virtual void clear() { m_groupedRootNode->clear(); } protected: ProblemStoreNode *m_rootNode; QScopedPointer m_groupedRootNode; }; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements no grouping strategy, that is just stores the problems without any grouping class NoGroupingStrategy final : public GroupingStrategy { public: NoGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); m_groupedRootNode->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on path class PathGroupingStrategy final : public GroupingStrategy { public: PathGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { } void addProblem(const IProblem::Ptr &problem) override { QString path = problem->finalLocation().document.str(); /// See if we already have this path ProblemStoreNode *parent = nullptr; foreach (ProblemStoreNode *node, m_groupedRootNode->children()) { if (node->label() == path) { parent = node; break; } } /// If not add it! if (parent == nullptr) { parent = new LabelNode(m_groupedRootNode.data(), path); m_groupedRootNode->addChild(parent); } ProblemNode *node = new ProblemNode(parent, problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Implements grouping based on severity class SeverityGroupingStrategy final : public GroupingStrategy { public: enum SeverityGroups { GroupError = 0, GroupWarning = 1, GroupHint = 2 }; SeverityGroupingStrategy(ProblemStoreNode *root) : GroupingStrategy(root) { /// Create the groups on construction, so there's no need to search for them on addition m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Error"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Warning"))); m_groupedRootNode->addChild(new LabelNode(m_groupedRootNode.data(), i18n("Hint"))); } void addProblem(const IProblem::Ptr &problem) override { ProblemStoreNode *parent = nullptr; switch (problem->severity()) { case IProblem::Error: parent = m_groupedRootNode->child(GroupError); break; case IProblem::Warning: parent = m_groupedRootNode->child(GroupWarning); break; case IProblem::Hint: parent = m_groupedRootNode->child(GroupHint); break; } ProblemNode *node = new ProblemNode(m_groupedRootNode.data(), problem); addDiagnostics(node, problem->diagnostics()); parent->addChild(node); } void clear() override { m_groupedRootNode->child(GroupError)->clear(); m_groupedRootNode->child(GroupWarning)->clear(); m_groupedRootNode->child(GroupHint)->clear(); } }; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace KDevelop { struct FilteredProblemStorePrivate { FilteredProblemStorePrivate(FilteredProblemStore* q) : q(q) , m_strategy(new NoGroupingStrategy(q->rootNode())) , m_grouping(NoGrouping) , m_bypassScopeFilter(false) { } /// Tells if the problem matches the filters bool match(const IProblem::Ptr &problem) const; FilteredProblemStore* q; QScopedPointer m_strategy; GroupingMethod m_grouping; bool m_bypassScopeFilter; }; FilteredProblemStore::FilteredProblemStore(QObject *parent) : ProblemStore(parent) , d(new FilteredProblemStorePrivate(this)) { } FilteredProblemStore::~FilteredProblemStore() { } void FilteredProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemStore::addProblem(problem); if (d->match(problem)) d->m_strategy->addProblem(problem); } const ProblemStoreNode* FilteredProblemStore::findNode(int row, ProblemStoreNode *parent) const { return d->m_strategy->findNode(row, parent); } int FilteredProblemStore::count(ProblemStoreNode *parent) const { return d->m_strategy->count(parent); } void FilteredProblemStore::clear() { d->m_strategy->clear(); ProblemStore::clear(); } void FilteredProblemStore::rebuild() { emit beginRebuild(); d->m_strategy->clear(); foreach (ProblemStoreNode *node, rootNode()->children()) { IProblem::Ptr problem = node->problem(); if (d->match(problem)) { d->m_strategy->addProblem(problem); } } emit endRebuild(); } void FilteredProblemStore::setGrouping(int grouping) { GroupingMethod g = GroupingMethod(grouping); if(g == d->m_grouping) return; d->m_grouping = g; switch (g) { case NoGrouping: d->m_strategy.reset(new NoGroupingStrategy(rootNode())); break; case PathGrouping: d->m_strategy.reset(new PathGroupingStrategy(rootNode())); break; case SeverityGrouping: d->m_strategy.reset(new SeverityGroupingStrategy(rootNode())); break; } rebuild(); emit changed(); } int FilteredProblemStore::grouping() const { return d->m_grouping; } void FilteredProblemStore::setBypassScopeFilter(bool bypass) { if (d->m_bypassScopeFilter != bypass) { d->m_bypassScopeFilter = bypass; rebuild(); emit changed(); } } bool FilteredProblemStore::bypassScopeFilter() const { return d->m_bypassScopeFilter; } bool FilteredProblemStorePrivate::match(const IProblem::Ptr &problem) const { - /// If the problem is less severe than our filter criterion then it's discarded - if(problem->severity() > q->severity()) - return false; + if(problem->severity()!=IProblem::NoSeverity) + { + /// If the problem severity isn't in the filter severities it's discarded + if(!q->severities().testFlag(problem->severity())) + return false; + } + else + { + if(!q->severities().testFlag(IProblem::Hint))//workaround for problems wothout correctly set severity + return false; + } /// If we have bypass on, don't check the scope if (!m_bypassScopeFilter) { /// If the problem isn't in a file that's in the watched document set, it's discarded const WatchedDocumentSet::DocumentSet &docs = q->documents()->get(); if(!docs.contains(problem->finalLocation().document)) return false; } return true; } } diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index 5c785cfa7e..e0556d67dd 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,325 +1,340 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * * 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 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 "problemmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QIcon iconForSeverity(KDevelop::IProblem::Severity severity) { switch (severity) { case KDevelop::IProblem::Hint: return QIcon::fromTheme(QStringLiteral("dialog-information")); case KDevelop::IProblem::Warning: return QIcon::fromTheme(QStringLiteral("dialog-warning")); case KDevelop::IProblem::Error: return QIcon::fromTheme(QStringLiteral("dialog-error")); } return {}; } } struct ProblemModelPrivate { ProblemModelPrivate(KDevelop::ProblemStore *store) : m_problems(store) , m_features(KDevelop::ProblemModel::NoFeatures) { } QScopedPointer m_problems; KDevelop::ProblemModel::Features m_features; }; namespace KDevelop { ProblemModel::ProblemModel(QObject * parent, ProblemStore *store) : QAbstractItemModel(parent) , d(new ProblemModelPrivate(store)) { if (!d->m_problems) { d->m_problems.reset(new FilteredProblemStore()); d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter; } setScope(CurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument); /// CompletionSettings include a list of todo markers we care for, so need to update connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate); if (ICore::self()->documentController()->activeDocument()) { setCurrentDocument(ICore::self()->documentController()->activeDocument()); } connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild); connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild); } ProblemModel::~ ProblemModel() { } int ProblemModel::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return d->m_problems->count(); } else { return d->m_problems->count(reinterpret_cast(parent.internalPointer())); } } static QString displayUrl(const QUrl &url, const QUrl &base) { if (base.isParentOf(url)) { return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length()); } else { return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain); } } QVariant ProblemModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename); IProblem::Ptr p = problemForIndex(index); if (!p.constData()) { if (role == Qt::DisplayRole && index.column() == Error) { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (node) { return node->label(); } } return {}; } if (role == SeverityRole) { return p->severity(); } else if (role == ProblemRole) { return QVariant::fromValue(p); } switch (role) { case Qt::DisplayRole: switch (index.column()) { case Source: return p->sourceString(); case Error: return p->description(); case File: return displayUrl(p->finalLocation().document.toUrl(), baseDirectory); case Line: if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().line() + 1); } break; case Column: if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().column() + 1); } break; } break; case Qt::DecorationRole: if (index.column() == Error) { return iconForSeverity(p->severity()); } break; case Qt::ToolTipRole: return p->explanation(); default: break; } return {}; } QModelIndex ProblemModel::parent(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } ProblemStoreNode *parent = node->parent(); if (!parent || parent->isRoot()) { return {}; } int idx = parent->index(); return createIndex(idx, 0, parent); } QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) { return QModelIndex(); } ProblemStoreNode *parentNode = reinterpret_cast(parent.internalPointer()); const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode); return createIndex(row, column, (void*)node); } int ProblemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return LastColumn; } IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); if (!node) { return {}; } else { return node->problem(); } } ProblemModel::Features ProblemModel::features() const { return d->m_features; } void ProblemModel::setFeatures(Features features) { d->m_features = features; } void ProblemModel::addProblem(const IProblem::Ptr &problem) { int c = d->m_problems->count(); beginInsertRows(QModelIndex(), c, c + 1); d->m_problems->addProblem(problem); endInsertRows(); } void ProblemModel::setProblems(const QVector &problems) { beginResetModel(); d->m_problems->setProblems(problems); endResetModel(); } void ProblemModel::clearProblems() { beginResetModel(); d->m_problems->clear(); endResetModel(); } QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role != Qt::DisplayRole) return {}; switch (section) { case Source: return i18nc("@title:column source of problem", "Source"); case Error: return i18nc("@title:column problem description", "Problem"); case File: return i18nc("@title:column file where problem was found", "File"); case Line: return i18nc("@title:column line number with problem", "Line"); case Column: return i18nc("@title:column column number with problem", "Column"); } return {}; } void ProblemModel::setCurrentDocument(IDocument* document) { Q_ASSERT(thread() == QThread::currentThread()); QUrl currentDocument = document->url(); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setCurrentDocument(IndexedString(currentDocument)); } void ProblemModel::onBeginRebuild() { beginResetModel(); } void ProblemModel::onEndRebuild() { endResetModel(); } void ProblemModel::setScope(int scope) { Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setScope(scope); } void ProblemModel::setSeverity(int severity) { - Q_ASSERT(thread() == QThread::currentThread()); + switch (severity) + { + case KDevelop::IProblem::Error: + setSeverities(KDevelop::IProblem::Error); + break; + case KDevelop::IProblem::Warning: + setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); + break; + case KDevelop::IProblem::Hint: + setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); + break; + } +} +void ProblemModel::setSeverities(KDevelop::IProblem::Severities severities) +{ + Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt - d->m_problems->setSeverity(severity); + d->m_problems->setSeverities(severities); } void ProblemModel::setGrouping(int grouping) { /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setGrouping(grouping); } ProblemStore* ProblemModel::store() const { return d->m_problems.data(); } } diff --git a/shell/problemmodel.h b/shell/problemmodel.h index 0e4810d52f..89c844874e 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,182 +1,184 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * Copyright 2015 Laszlo Kis-Adam * * 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 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 PROBLEMMODEL_H #define PROBLEMMODEL_H #include #include #include #include struct ProblemModelPrivate; namespace KDevelop { class IDocument; class ProblemStore; /** * @brief Wraps a ProblemStore and adds the QAbstractItemModel interface, so the it can be used in a model/view architecture. * * By default ProblemModel instantiates a FilteredProblemStore, with the following features on: * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Has to following columns: * \li Error * \li Source * \li File * \li Line * \li Column * \li LastColumn * * Possible ProblemModel features * \li NoFeatures * \li CanDoFullUpdate * \li CanShowImports * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Scope, severity, grouping, imports can be set using the slots named after these features. * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem); * problem->setDescription(QStringLiteral("Problem")); * ProblemModel *model = new ProblemModel(nullptr); * model->addProblem(problem); * model->rowCount(); // returns 1 * QModelIndex idx = model->index(0, 0); * model->data(index); // "Problem" * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModel : public QAbstractItemModel { Q_OBJECT public: /// List of supportable features enum FeatureCode { NoFeatures = 0, /// No features :( CanDoFullUpdate = 1, /// Reload/Reparse problems CanShowImports = 2, /// Show problems from imported files. E.g.: Header files in C/C++ ScopeFilter = 4, /// Filter problems by scope. E.g.: current document, open documents, etc SeverityFilter = 8, /// Filter problems by severity. E.g.: hint, warning, error, etc Grouping = 16, /// Can group problems CanByPassScopeFilter = 32 /// Can bypass scope filter }; Q_DECLARE_FLAGS(Features, FeatureCode) explicit ProblemModel(QObject *parent, ProblemStore *store = NULL); ~ProblemModel() override; enum Columns { Error, Source, File, Line, Column, LastColumn }; enum Roles { ProblemRole = Qt::UserRole + 1, SeverityRole }; int columnCount(const QModelIndex & parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex & index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; IProblem::Ptr problemForIndex(const QModelIndex& index) const; /// Adds a new problem to the model void addProblem(const IProblem::Ptr &problem); /// Clears the problems, then adds a new set of them void setProblems(const QVector &problems); /// Clears the problems void clearProblems(); /// Retrieve the supported features Features features() const; /// Set the supported features void setFeatures(Features features); public slots: /// Show imports virtual void setShowImports(bool){} /// Sets the scope filter. Uses int to be able to use QSignalMapper virtual void setScope(int scope); /// Sets the severity filter. Uses int to be able to use QSignalMapper - virtual void setSeverity(int severity); + virtual void setSeverity(int severity);///old-style severity filtering + + virtual void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity filtering void setGrouping(int grouping); /** * Force a full problem update. * E.g.: Reparse the source code. * Obviously it doesn't make sense for run-time problem checkers. */ virtual void forceFullUpdate(){} protected slots: /// Triggered when problems change virtual void onProblemsChanged(){} private slots: /// Triggered when the current document changes virtual void setCurrentDocument(IDocument* doc); /// Triggered before the problems are rebuilt void onBeginRebuild(); /// Triggered once the problems have been rebuilt void onEndRebuild(); protected: ProblemStore *store() const; private: QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProblemModel::Features) } #endif // PROBLEMMODEL_H diff --git a/shell/problemstore.cpp b/shell/problemstore.cpp index c1bbe50e0f..cfd4911f76 100644 --- a/shell/problemstore.cpp +++ b/shell/problemstore.cpp @@ -1,211 +1,238 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 "problemstore.h" #include #include #include "problemstorenode.h" struct ProblemStorePrivate { ProblemStorePrivate() : m_documents(nullptr) - , m_severity(KDevelop::IProblem::Hint) + , m_severities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint) , m_rootNode(new KDevelop::ProblemStoreNode()) { } /// Watched document set. Only problems that are in files in this set are stored. KDevelop::WatchedDocumentSet *m_documents; /// The severity filter setting - int m_severity; + KDevelop::IProblem::Severities m_severities; /// The problems list KDevelop::ProblemStoreNode *m_rootNode; /// Path of the currently open document KDevelop::IndexedString m_currentDocument; }; namespace KDevelop { ProblemStore::ProblemStore(QObject *parent) : QObject(parent), d(new ProblemStorePrivate) { } ProblemStore::~ProblemStore() { clear(); delete d->m_rootNode; } void ProblemStore::addProblem(const IProblem::Ptr &problem) { ProblemNode *node = new ProblemNode(d->m_rootNode); node->setProblem(problem); d->m_rootNode->addChild(node); } void ProblemStore::setProblems(const QVector &problems) { clear(); foreach (IProblem::Ptr problem, problems) { d->m_rootNode->addChild(new ProblemNode(d->m_rootNode, problem)); } rebuild(); } const ProblemStoreNode* ProblemStore::findNode(int row, ProblemStoreNode *parent) const { Q_UNUSED(parent); return d->m_rootNode->child(row); } int ProblemStore::count(ProblemStoreNode *parent) const { if(parent) return parent->count(); else return d->m_rootNode->count(); } void ProblemStore::clear() { d->m_rootNode->clear(); } void ProblemStore::rebuild() { } void ProblemStore::setSeverity(int severity) { - if(severity != d->m_severity) + switch (severity) { - d->m_severity = severity; + case KDevelop::IProblem::Error: + setSeverities(KDevelop::IProblem::Error); + break; + case KDevelop::IProblem::Warning: + setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning); + break; + case KDevelop::IProblem::Hint: + setSeverities(KDevelop::IProblem::Error | KDevelop::IProblem::Warning | KDevelop::IProblem::Hint); + break; + } +} + +void ProblemStore::setSeverities(KDevelop::IProblem::Severities severities) +{ + if(severities != d->m_severities) + { + d->m_severities = severities; rebuild(); emit changed(); } } int ProblemStore::severity() const { - return d->m_severity; + if (d->m_severities.testFlag(KDevelop::IProblem::Hint)) + return KDevelop::IProblem::Hint; + if (d->m_severities.testFlag(KDevelop::IProblem::Warning)) + return KDevelop::IProblem::Warning; + if (d->m_severities.testFlag(KDevelop::IProblem::Error)) + return KDevelop::IProblem::Error; + return 0; +} + +KDevelop::IProblem::Severities ProblemStore::severities() const +{ + return d->m_severities; } WatchedDocumentSet* ProblemStore::documents() const { return d->m_documents; } void ProblemStore::setScope(int scope) { ProblemScope cast_scope = static_cast(scope); if (cast_scope == BypassScopeFilter) { setBypassScopeFilter(true); return; } setBypassScopeFilter(false); if (d->m_documents) { if(cast_scope == d->m_documents->getScope()) return; delete d->m_documents; } switch (cast_scope) { case CurrentDocument: d->m_documents = new CurrentDocumentSet(d->m_currentDocument, this); break; case OpenDocuments: d->m_documents = new OpenDocumentSet(this); break; case CurrentProject: d->m_documents = new CurrentProjectSet(d->m_currentDocument, this); break; case AllProjects: d->m_documents = new AllProjectSet(this); break; case BypassScopeFilter: // handled above break; } rebuild(); connect(d->m_documents, &WatchedDocumentSet::changed, this, &ProblemStore::onDocumentSetChanged); emit changed(); } int ProblemStore::scope() const { Q_ASSERT(d->m_documents != nullptr); return d->m_documents->getScope(); } void ProblemStore::setGrouping(int grouping) { Q_UNUSED(grouping); } void ProblemStore::setBypassScopeFilter(bool bypass) { Q_UNUSED(bypass); } void ProblemStore::setCurrentDocument(const IndexedString &doc) { d->m_currentDocument = doc; d->m_documents->setCurrentDocument(doc); } const KDevelop::IndexedString& ProblemStore::currentDocument() const { return d->m_currentDocument; } void ProblemStore::onDocumentSetChanged() { rebuild(); emit changed(); } ProblemStoreNode* ProblemStore::rootNode() { return d->m_rootNode; } } diff --git a/shell/problemstore.h b/shell/problemstore.h index 4209e8043b..7ca137f38c 100644 --- a/shell/problemstore.h +++ b/shell/problemstore.h @@ -1,148 +1,152 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 PROBLEMSTORE_H #define PROBLEMSTORE_H #include #include #include #include #include struct ProblemStorePrivate; namespace KDevelop { class WatchedDocumentSet; class ProblemStoreNode; /** * @brief Stores and handles problems. Does no ordering or filtering, those should be done in subclasses. * * Used to store problems that are ordered, filtered somewhere else. For example: DUChain problems gathered by ProblemReporter. * Stores the problems in ProblemStoreNodes. * When implementing a subclass, first and foremost the rebuild method needs to be implemented, which is called every time there's a change in scope and severity filter. * If grouping is desired then also the setGrouping method must be implemented. * ProblemStore depending on settings uses CurrentDocumentSet, OpenDocumentSet, CurrentProjectSet, or AllProjectSet for scope support (NOTE: Filtering still has to be implemented in either a subclass, or somewhere else). * When the scope changes it emits the changed() signal. * * Scope set / query methods: * \li setScope() * \li scope() * * Valid scope settings: * \li CurrentDocument * \li OpenDocuments * \li CurrentProject * \li AllProjects * \li BypassScopeFilter * * Usage example: * @code * QVector problems; * // Add 4 problems * ... * ProblemStore *store = new ProblemStore(); * store->setProblems(problems); * store->count(); // Returns 4 * * ProblemStoreNode *node = store->findNode(0); // Returns the node with the first problem * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemStore : public QObject { Q_OBJECT public: explicit ProblemStore(QObject *parent = nullptr); ~ProblemStore() override; /// Adds a problem virtual void addProblem(const IProblem::Ptr &problem); /// Clears the current problems, and adds new ones from a list virtual void setProblems(const QVector &problems); /// Finds the specified node virtual const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const; /// Returns the number of problems virtual int count(ProblemStoreNode *parent = nullptr) const; /// Clears the problems virtual void clear(); /// Rebuild the problems list, if applicable. It does nothing in the base class. virtual void rebuild(); /// Specifies the severity filter - virtual void setSeverity(int severity); + virtual void setSeverity(int severity);///old-style severity access + + virtual void setSeverities(KDevelop::IProblem::Severities severities);///new-style severity access /// Retrives the severity filter settings - int severity() const; + int severity() const;///old-style severity access + + KDevelop::IProblem::Severities severities() const;//new-style severity access /// Retrieves the currently watched document set WatchedDocumentSet* documents() const; /// Sets the scope filter void setScope(int scope); /// Returns the current scope int scope() const; /// Sets the grouping method virtual void setGrouping(int grouping); /// Sets whether we should bypass the scope filter virtual void setBypassScopeFilter(bool bypass); /// Sets the currently shown document (in the editor, it's triggered by the IDE) void setCurrentDocument(const IndexedString &doc); /// Retrives the path of the current document const KDevelop::IndexedString& currentDocument() const; signals: /// Emitted when the problems change void changed(); /// Emitted before the problemlist is rebuilt void beginRebuild(); /// Emitted once the problemlist has been rebuilt void endRebuild(); private slots: /// Triggered when the watched document set changes. E.g.:document closed, new one added, etc virtual void onDocumentSetChanged(); protected: ProblemStoreNode* rootNode(); private: QScopedPointer d; }; } #endif diff --git a/shell/tests/test_filteredproblemstore.cpp b/shell/tests/test_filteredproblemstore.cpp index 7693505613..f9b5dcbe7d 100644 --- a/shell/tests/test_filteredproblemstore.cpp +++ b/shell/tests/test_filteredproblemstore.cpp @@ -1,511 +1,698 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 #include #include #include #include #include #include #include #include namespace { const int ErrorCount = 1; -const int WarningCount = 1; -const int HintCount = 1; +const int WarningCount = 2; +const int HintCount = 3; +const int ProblemsCount = ErrorCount + WarningCount + HintCount; const int ErrorFilterProblemCount = ErrorCount; const int WarningFilterProblemCount = ErrorCount + WarningCount; const int HintFilterProblemCount = ErrorCount + WarningCount + HintCount; } using namespace KDevelop; class TestFilteredProblemStore : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testBypass(); void testSeverity(); + void testSeverities(); void testGrouping(); void testNoGrouping(); void testPathGrouping(); void testSeverityGrouping(); private: // Severity grouping testing bool checkCounts(int error, int warning, int hint); bool checkNodeLabels(); // --------------------------- void generateProblems(); QScopedPointer m_store; QVector m_problems; IProblem::Ptr m_diagnosticTestProblem; }; #define MYCOMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ return false #define MYVERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return false void TestFilteredProblemStore::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_store.reset(new FilteredProblemStore()); generateProblems(); } void TestFilteredProblemStore::cleanupTestCase() { TestCore::shutdown(); } void TestFilteredProblemStore::testBypass() { QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); QVERIFY(!m_store->bypassScopeFilter()); m_store->setBypassScopeFilter(true); QVERIFY(m_store->bypassScopeFilter()); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); } void TestFilteredProblemStore::testSeverity() { QVERIFY(m_store->severity() == IProblem::Hint); QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); m_store->setSeverity(IProblem::Error); QVERIFY(m_store->severity() == IProblem::Error); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); m_store->setSeverity(IProblem::Hint); } +void TestFilteredProblemStore::testSeverities() +{ + QVERIFY(m_store->severities() == (IProblem::Error | IProblem::Warning | IProblem::Hint)); + + QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); + QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); + QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); + + m_store->setSeverities(IProblem::Error | IProblem::Hint); + + QVERIFY(m_store->severities() == (IProblem::Error | IProblem::Hint)); + + QCOMPARE(changedSpy.count(), 1); + QCOMPARE(beginRebuildSpy.count(), 1); + QCOMPARE(endRebuildSpy.count(), 1); + + m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); +} + void TestFilteredProblemStore::testGrouping() { QVERIFY(m_store->grouping() == NoGrouping); QSignalSpy changedSpy(m_store.data(), &FilteredProblemStore::changed); QSignalSpy beginRebuildSpy(m_store.data(), &FilteredProblemStore::beginRebuild); QSignalSpy endRebuildSpy(m_store.data(), &FilteredProblemStore::endRebuild); m_store->setGrouping(PathGrouping); QVERIFY(m_store->grouping() == PathGrouping); QCOMPARE(changedSpy.count(), 1); QCOMPARE(beginRebuildSpy.count(), 1); QCOMPARE(endRebuildSpy.count(), 1); m_store->setGrouping(NoGrouping); } // Compares the node and it's children to a reference problem and it's diagnostics bool checkDiagnodes(const ProblemStoreNode *node, const IProblem::Ptr &reference) { const ProblemNode *problemNode = dynamic_cast(node); MYVERIFY(problemNode != nullptr); MYCOMPARE(problemNode->problem()->description(), reference->description()); MYCOMPARE(problemNode->problem()->finalLocation().document.str(), reference->finalLocation().document.str()); MYCOMPARE(problemNode->count(), 1); const IProblem::Ptr diag = reference->diagnostics().at(0); const ProblemNode *diagNode = dynamic_cast(problemNode->child(0)); MYVERIFY(diagNode != nullptr); MYCOMPARE(diagNode->problem()->description(), diag->description()); MYCOMPARE(diagNode->count(), 1); const IProblem::Ptr diagdiag = diag->diagnostics().at(0); const ProblemNode *diagdiagNode = dynamic_cast(diagNode->child(0)); MYVERIFY(diagdiagNode != nullptr); MYCOMPARE(diagdiagNode->problem()->description(), diagdiag->description()); MYCOMPARE(diagdiagNode->count(), 0); return true; } void TestFilteredProblemStore::testNoGrouping() { // Add problems int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_store->addProblem(p); c++; QCOMPARE(m_store->count(), c); } for (int i = 0; i < c; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node != nullptr); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Check if clear works m_store->clear(); QCOMPARE(m_store->count(), 0); // Set problems m_store->setProblems(m_problems); QCOMPARE(m_problems.count(), m_store->count()); for (int i = 0; i < c; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node != nullptr); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } - // Check severity filtering + // Check old style severity filtering + // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), ErrorFilterProblemCount); - - { + for (int i = 0; i < ErrorFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(0)); QVERIFY(node != nullptr); - QCOMPARE(node->problem()->description(), m_problems[0]->description()); + QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), WarningFilterProblemCount); for (int i = 0; i < WarningFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node != nullptr); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), HintFilterProblemCount); for (int i = 0; i < HintFilterProblemCount; i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node != nullptr); QCOMPARE(node->problem()->description(), m_problems[i]->description()); } + // Check new severity filtering + // Error filter + m_store->setSeverities(IProblem::Error); + QCOMPARE(m_store->count(), ErrorCount); + for (int i = 0; i < ErrorCount; i++) { + const ProblemNode *node = dynamic_cast(m_store->findNode(i)); + QVERIFY(node != nullptr); + QCOMPARE(node->problem()->description(), m_problems[i]->description()); + } + + // Warning filter + m_store->setSeverities(IProblem::Warning); + QCOMPARE(m_store->count(), WarningCount); + for (int i = 0; i < WarningCount; i++) { + const ProblemNode *node = dynamic_cast(m_store->findNode(i)); + QVERIFY(node != nullptr); + + QCOMPARE(node->problem()->description(), m_problems[i+ErrorCount]->description()); + } + + // Hint filter + m_store->setSeverities(IProblem::Hint); + QCOMPARE(m_store->count(), HintCount); + for (int i = 0; i < HintCount; i++) { + const ProblemNode *node = dynamic_cast(m_store->findNode(i)); + QVERIFY(node != nullptr); + + QCOMPARE(node->problem()->description(), m_problems[i+ErrorCount+WarningCount]->description()); + } + + //Error + Hint filter + m_store->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_store->count(), HintCount + ErrorCount); + for (int i = 0; i < ErrorCount; i++) { + const ProblemNode *node = dynamic_cast(m_store->findNode(i)); + QVERIFY(node != nullptr); + + QCOMPARE(node->problem()->description(), m_problems[i]->description()); + } + for (int i = ErrorCount; i < ErrorCount+HintCount; i++) { + const ProblemNode *node = dynamic_cast(m_store->findNode(i)); + QVERIFY(node != nullptr); + + QCOMPARE(node->problem()->description(), m_problems[i+WarningCount]->description()); + } + + m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QCOMPARE(m_store->count(), 1); QVERIFY(checkDiagnodes(m_store->findNode(0), m_diagnosticTestProblem)); } bool checkNodeLabel(const ProblemStoreNode *node, const QString &label) { const LabelNode *parent = dynamic_cast(node); MYVERIFY(parent != nullptr); MYCOMPARE(parent->label(), label); return true; } bool checkNodeDescription(const ProblemStoreNode *node, const QString &descr) { const ProblemNode *n = dynamic_cast(node); MYVERIFY(n != nullptr); MYCOMPARE(n->problem()->description(), descr); return true; } void TestFilteredProblemStore::testPathGrouping() { m_store->clear(); // Rebuild the problem list with grouping m_store->setGrouping(PathGrouping); // Add problems foreach (const IProblem::Ptr &p, m_problems) { m_store->addProblem(p); } - QCOMPARE(m_store->count(), 3); + QCOMPARE(m_store->count(), ProblemsCount); for (int i = 0; i < 3; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Now add a new problem IProblem::Ptr p(new DetectedProblem()); p->setDescription(QStringLiteral("PROBLEM4")); p->setFinalLocation(m_problems[2]->finalLocation()); m_store->addProblem(p); - QCOMPARE(m_store->count(), 3); + QCOMPARE(m_store->count(), ProblemsCount); // Check the first 2 top-nodes for (int i = 0; i < 2; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // check the last one, and check the added problem is at the right place { const ProblemStoreNode *node = m_store->findNode(2); checkNodeLabel(node, m_problems[2]->finalLocation().document.str()); QCOMPARE(node->count(), 2); checkNodeDescription(node->child(1), p->description()); } m_store->clear(); m_store->setProblems(m_problems); // Check filters + // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), ErrorFilterProblemCount); { const ProblemStoreNode *node = m_store->findNode(0); checkNodeLabel(node, m_problems[0]->finalLocation().document.str()); - QCOMPARE(node->count(), ErrorCount); + QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[0]->description()); } // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), WarningFilterProblemCount); for (int i = 0; i < WarningFilterProblemCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); - QCOMPARE(node->count(), WarningCount); + QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), HintFilterProblemCount); for (int i = 0; i < HintFilterProblemCount; i++) { const ProblemStoreNode *node = m_store->findNode(i); checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); - QCOMPARE(node->count(), HintCount); + QCOMPARE(node->count(), 1); + checkNodeDescription(node->child(0), m_problems[i]->description()); + } + + // Check new severity filtering + // Error filter + m_store->setSeverities(IProblem::Error); + for (int i = 0; i < ErrorCount; i++) { + const ProblemStoreNode *node = m_store->findNode(i); + checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); + QCOMPARE(node->count(), 1); + checkNodeDescription(node->child(0), m_problems[i]->description()); + } + + // Warning filter + m_store->setSeverities(IProblem::Warning); + for (int i = 0; i < WarningCount; i++) { + const ProblemStoreNode *node = m_store->findNode(i); + checkNodeLabel(node, m_problems[i+ErrorCount]->finalLocation().document.str()); + QCOMPARE(node->count(), 1); + checkNodeDescription(node->child(0), m_problems[i+ErrorCount]->description()); + } + + // Hint filter + m_store->setSeverities(IProblem::Hint); + for (int i = 0; i < HintCount; i++) { + const ProblemStoreNode *node = m_store->findNode(i); + checkNodeLabel(node, m_problems[i+ErrorCount+WarningCount]->finalLocation().document.str()); + QCOMPARE(node->count(), 1); + checkNodeDescription(node->child(0), m_problems[i+ErrorCount+WarningCount]->description()); + } + + //Error + Hint filter + m_store->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_store->count(), HintCount + ErrorCount); + for (int i = 0; i < ErrorCount; i++) { + const ProblemStoreNode *node = m_store->findNode(i); + checkNodeLabel(node, m_problems[i]->finalLocation().document.str()); + QCOMPARE(node->count(), 1); checkNodeDescription(node->child(0), m_problems[i]->description()); } + for (int i = ErrorCount; i < ErrorCount+HintCount; i++) { + const ProblemStoreNode *node = m_store->findNode(i); + checkNodeLabel(node, m_problems[i+WarningCount]->finalLocation().document.str()); + QCOMPARE(node->count(), 1); + checkNodeDescription(node->child(0), m_problems[i+WarningCount]->description()); + } + + m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if the diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QCOMPARE(m_store->count(), 1); const LabelNode *node = dynamic_cast(m_store->findNode(0)); QVERIFY(node != nullptr); QCOMPARE(node->label(), m_diagnosticTestProblem->finalLocation().document.str()); QVERIFY(checkDiagnodes(node->child(0), m_diagnosticTestProblem)); } void TestFilteredProblemStore::testSeverityGrouping() { m_store->clear(); m_store->setGrouping(SeverityGrouping); QCOMPARE(m_store->count(), 3); const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); // Add problems - int c = 0; - foreach (const IProblem::Ptr &p, m_problems) { - m_store->addProblem(p); - - QCOMPARE(m_store->findNode(c)->count(), 1); - c++; + for (int i=0;iaddProblem(m_problems[i]); + int severityType = 0; //error + int addedCountOfCurrentSeverityType = i + 1; + if (i>=ErrorCount) + { + severityType = 1; //warning + addedCountOfCurrentSeverityType = i - ErrorCount + 1; + } + if (i>=ErrorCount+WarningCount) + { + severityType = 2; //hint + addedCountOfCurrentSeverityType = i - (ErrorCount + WarningCount) + 1; + } + QCOMPARE(m_store->findNode(severityType)->count(), addedCountOfCurrentSeverityType); } + QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); - checkNodeDescription(hintNode->child(0), m_problems[2]->description()); + checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Clear m_store->clear(); QCOMPARE(m_store->count(), 3); QVERIFY(checkCounts(0,0,0)); // Set problems m_store->setProblems(m_problems); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); - checkNodeDescription(hintNode->child(0), m_problems[2]->description()); + checkNodeDescription(hintNode->child(0), m_problems[3]->description()); // Check severity filter + // old-style setSeverity // Error filter m_store->setSeverity(IProblem::Error); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, 0, 0)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); // Warning filter m_store->setSeverity(IProblem::Warning); QCOMPARE(m_store->count(), 3); checkNodeLabels(); QVERIFY(checkCounts(ErrorCount, WarningCount, 0)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); // Hint filter m_store->setSeverity(IProblem::Hint); QCOMPARE(m_store->count(), 3); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(ErrorCount, WarningCount, HintCount)); checkNodeDescription(errorNode->child(0), m_problems[0]->description()); checkNodeDescription(warningNode->child(0), m_problems[1]->description()); - checkNodeDescription(hintNode->child(0), m_problems[2]->description()); + checkNodeDescription(hintNode->child(0), m_problems[3]->description()); + + // Check severity filter + // Error filter + m_store->setSeverities(IProblem::Error); + QCOMPARE(m_store->count(), 3); + QVERIFY(checkNodeLabels()); + QVERIFY(checkCounts(ErrorCount, 0, 0)); + checkNodeDescription(errorNode->child(0), m_problems[0]->description()); + + // Warning filter + m_store->setSeverities(IProblem::Warning); + QCOMPARE(m_store->count(), 3); + QVERIFY(checkNodeLabels()); + QVERIFY(checkCounts(0, WarningCount, 0)); + checkNodeDescription(warningNode->child(0), m_problems[1]->description()); + + // Hint filter + m_store->setSeverities(IProblem::Hint); + QCOMPARE(m_store->count(), 3); + QVERIFY(checkNodeLabels()); + QVERIFY(checkCounts(0, 0, HintCount)); + checkNodeDescription(hintNode->child(0), m_problems[3]->description()); + + // Error + Hint filter + m_store->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_store->count(), 3); + QVERIFY(checkNodeLabels()); + QVERIFY(checkCounts(ErrorCount, 0, HintCount)); + checkNodeDescription(errorNode->child(0), m_problems[0]->description()); + checkNodeDescription(hintNode->child(0), m_problems[3]->description()); + + m_store->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); m_store->clear(); // Check if diagnostics are added properly m_store->addProblem(m_diagnosticTestProblem); QVERIFY(checkNodeLabels()); QVERIFY(checkCounts(1, 0, 0)); QVERIFY(checkDiagnodes(m_store->findNode(0)->child(0), m_diagnosticTestProblem)); } bool TestFilteredProblemStore::checkCounts(int error, int warning, int hint) { const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); MYVERIFY(errorNode != nullptr); MYVERIFY(warningNode != nullptr); MYVERIFY(hintNode != nullptr); MYCOMPARE(errorNode->count(), error); MYCOMPARE(warningNode->count(), warning); MYCOMPARE(hintNode->count(), hint); return true; } bool TestFilteredProblemStore::checkNodeLabels() { const ProblemStoreNode *errorNode = m_store->findNode(0); const ProblemStoreNode *warningNode = m_store->findNode(1); const ProblemStoreNode *hintNode = m_store->findNode(2); MYCOMPARE(checkNodeLabel(errorNode, i18n("Error")), true); MYCOMPARE(checkNodeLabel(warningNode, i18n("Warning")), true); MYCOMPARE(checkNodeLabel(hintNode, i18n("Hint")), true); return true; } // Generate 3 problems, all with different paths, different severity // Also generates a problem with diagnostics void TestFilteredProblemStore::generateProblems() { IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); IProblem::Ptr p3(new DetectedProblem()); + IProblem::Ptr p4(new DetectedProblem()); + IProblem::Ptr p5(new DetectedProblem()); + IProblem::Ptr p6(new DetectedProblem()); DocumentRange r1; r1.document = IndexedString("/just/a/random/path"); p1->setDescription(QStringLiteral("PROBLEM1")); p1->setSeverity(IProblem::Error); p1->setFinalLocation(r1); DocumentRange r2; r2.document = IndexedString("/just/another/path"); p2->setDescription(QStringLiteral("PROBLEM2")); p2->setSeverity(IProblem::Warning); p2->setFinalLocation(r2); DocumentRange r3; - r3.document = IndexedString("/yet/another/test/path"); + r3.document = IndexedString("/just/another/pathy/patha"); - p2->setDescription(QStringLiteral("PROBLEM3")); - p3->setSeverity(IProblem::Hint); + p3->setDescription(QStringLiteral("PROBLEM3")); + p3->setSeverity(IProblem::Warning); p3->setFinalLocation(r3); + DocumentRange r4; + r4.document = IndexedString("/yet/another/test/path"); + + p4->setDescription(QStringLiteral("PROBLEM4")); + p4->setSeverity(IProblem::Hint); + p4->setFinalLocation(r4); + + DocumentRange r5; + r5.document = IndexedString("/yet/another/pathy/test/path"); + + p5->setDescription(QStringLiteral("PROBLEM5")); + p5->setSeverity(IProblem::Hint); + p5->setFinalLocation(r5); + + DocumentRange r6; + r6.document = IndexedString("/yet/another/test/pathy/path"); + + p6->setDescription(QStringLiteral("PROBLEM6")); + p6->setSeverity(IProblem::Hint); + p6->setFinalLocation(r6); + + m_problems.push_back(p1); m_problems.push_back(p2); m_problems.push_back(p3); + m_problems.push_back(p4); + m_problems.push_back(p5); + m_problems.push_back(p6); // Problem for diagnostic testing IProblem::Ptr p(new DetectedProblem()); DocumentRange r; r.document = IndexedString("DIAGTEST"); p->setFinalLocation(r); p->setDescription(QStringLiteral("PROBLEM")); p->setSeverity(IProblem::Error); IProblem::Ptr d(new DetectedProblem()); d->setDescription(QStringLiteral("DIAG")); IProblem::Ptr dd(new DetectedProblem()); dd->setDescription(QStringLiteral("DIAGDIAG")); d->addDiagnostic(dd); p->addDiagnostic(d); m_diagnosticTestProblem = p; } QTEST_MAIN(TestFilteredProblemStore) #include "test_filteredproblemstore.moc" diff --git a/shell/tests/test_problemmodel.cpp b/shell/tests/test_problemmodel.cpp index e0bd4752fc..70dc1c1aeb 100644 --- a/shell/tests/test_problemmodel.cpp +++ b/shell/tests/test_problemmodel.cpp @@ -1,438 +1,515 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 #include #include #include #include #include #include #include #define MYCOMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ return false #define MYVERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return false using namespace KDevelop; class TestProblemModel : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testNoGrouping(); void testPathGrouping(); void testSeverityGrouping(); private: void generateProblems(); bool checkIsSame(int row, const QModelIndex &parent, const IProblem::Ptr &problem); bool checkDiagnostics(int row, const QModelIndex &parent); bool checkDisplay(int row, const QModelIndex &parent, const IProblem::Ptr &problem); bool checkLabel(int row, const QModelIndex &parent, const QString &label); bool checkPathGroup(int row, const IProblem::Ptr &problem); bool checkSeverityGroup(int row, const IProblem::Ptr &problem); QScopedPointer m_model; QVector m_problems; IProblem::Ptr m_diagnosticTestProblem; }; void TestProblemModel::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_model.reset(new ProblemModel(nullptr)); m_model->setScope(BypassScopeFilter); generateProblems(); } void TestProblemModel::cleanupTestCase() { TestCore::shutdown(); } void TestProblemModel::testNoGrouping() { m_model->setGrouping(NoGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 0); // Check if setting the problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkIsSame(i, QModelIndex(), m_problems[i])); } // Check if displaying various data parts works QVERIFY(checkDisplay(0, QModelIndex(), m_problems[0])); // Check if clearing the problems works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 0); // Check if adding the problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); c++; QCOMPARE(m_model->rowCount(), c); } for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkIsSame(i, QModelIndex(), m_problems[i])); } // Check if filtering works + // old-style setSeverity // Error filter m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); // Warning filter m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); QVERIFY(checkIsSame(1, QModelIndex(), m_problems[1])); // Hint filter m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); QVERIFY(checkIsSame(1, QModelIndex(), m_problems[1])); QVERIFY(checkIsSame(2, QModelIndex(), m_problems[2])); + // Check if filtering works + // new style + // Error filter + m_model->setSeverities(IProblem::Error); + QCOMPARE(m_model->rowCount(), 1); + QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); + + // Warning filter + m_model->setSeverities(IProblem::Warning); + QCOMPARE(m_model->rowCount(), 1); + QVERIFY(checkIsSame(0, QModelIndex(), m_problems[1])); + + // Hint filter + m_model->setSeverities(IProblem::Hint); + QCOMPARE(m_model->rowCount(), 1); + QVERIFY(checkIsSame(0, QModelIndex(), m_problems[2])); + + // Error + Hint filter + m_model->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_model->rowCount(), 2); + QVERIFY(checkIsSame(0, QModelIndex(), m_problems[0])); + QVERIFY(checkIsSame(1, QModelIndex(), m_problems[2])); + + m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); + // Check if diagnostics are added properly m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); QVERIFY(checkDiagnostics(0, QModelIndex())); m_model->clearProblems(); } void TestProblemModel::testPathGrouping() { m_model->setGrouping(PathGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 0); // Check if setting problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkLabel(i, QModelIndex(), m_problems[i]->finalLocation().document.str())); QModelIndex idx = m_model->index(i, 0); QVERIFY(idx.isValid()); QVERIFY(checkIsSame(0, idx, m_problems[i])); } // Check if displaying various data parts works { QModelIndex idx = m_model->index(0, 0); QVERIFY(idx.isValid()); QVERIFY(checkDisplay(0, idx, m_problems[0])); } // Check if clearing works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 0); // Check if add problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); c++; QCOMPARE(m_model->rowCount(), c); } for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkLabel(i, QModelIndex(), m_problems[i]->finalLocation().document.str())); QModelIndex idx = m_model->index(i, 0); QVERIFY(idx.isValid()); QVERIFY(checkIsSame(0, idx, m_problems[i])); } // Check if filtering works + // old-style setSeverity // Error filtering m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 1); QVERIFY(checkPathGroup(0, m_problems[0])); // Warning filtering m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 2); QVERIFY(checkPathGroup(0, m_problems[0])); QVERIFY(checkPathGroup(1, m_problems[1])); // Hint filtering m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); QVERIFY(checkPathGroup(0, m_problems[0])); QVERIFY(checkPathGroup(1, m_problems[1])); QVERIFY(checkPathGroup(2, m_problems[2])); + // Check if filtering works + // new style + // Error filtering + m_model->setSeverities(IProblem::Error); + QCOMPARE(m_model->rowCount(), 1); + QVERIFY(checkPathGroup(0, m_problems[0])); + + // Warning filtering + m_model->setSeverities(IProblem::Warning); + QCOMPARE(m_model->rowCount(), 1); + QVERIFY(checkPathGroup(0, m_problems[1])); + + // Hint filtering + m_model->setSeverities(IProblem::Hint); + QCOMPARE(m_model->rowCount(), 1);; + QVERIFY(checkPathGroup(0, m_problems[2])); + + // Error + Hint filtering + m_model->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_model->rowCount(), 2); + QVERIFY(checkPathGroup(0, m_problems[0])); + QVERIFY(checkPathGroup(1, m_problems[2])); + + m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); + // Check if diagnostics get to the right place m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); { QModelIndex parent = m_model->index(0, 0); QVERIFY(parent.isValid()); checkDiagnostics(0, parent); } m_model->clearProblems(); } void TestProblemModel::testSeverityGrouping() { m_model->setGrouping(SeverityGrouping); m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); // Check if setting problems works m_model->setProblems(m_problems); QCOMPARE(m_model->rowCount(), 3); for (int i = 0; i < m_model->rowCount(); i++) { QVERIFY(checkSeverityGroup(i, m_problems[i])); } // Check if displaying works for (int i = 0; i < m_model->rowCount(); i++) { QModelIndex parent = m_model->index(i, 0); QVERIFY(parent.isValid()); QVERIFY(checkDisplay(0, parent, m_problems[i])); } // Check if clearing works m_model->clearProblems(); QCOMPARE(m_model->rowCount(), 3); // Check if adding problems works int c = 0; foreach (const IProblem::Ptr &p, m_problems) { m_model->addProblem(p); QVERIFY(checkSeverityGroup(c, m_problems[c])); c++; } // Check if filtering works + // old-style setSeverity // Error filtering m_model->setSeverity(IProblem::Error); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); // Warning filtering m_model->setSeverity(IProblem::Warning); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); checkSeverityGroup(1, m_problems[1]); // Hint filtering m_model->setSeverity(IProblem::Hint); QCOMPARE(m_model->rowCount(), 3); checkSeverityGroup(0, m_problems[0]); checkSeverityGroup(1, m_problems[1]); checkSeverityGroup(2, m_problems[2]); + // Check if filtering works + // Error filtering + m_model->setSeverities(IProblem::Error); + QCOMPARE(m_model->rowCount(), 3); + checkSeverityGroup(0, m_problems[0]); + + // Warning filtering + m_model->setSeverities(IProblem::Warning); + QCOMPARE(m_model->rowCount(), 3); + checkSeverityGroup(1, m_problems[1]); + + // Hint filtering + m_model->setSeverities(IProblem::Hint); + QCOMPARE(m_model->rowCount(), 3); + checkSeverityGroup(2, m_problems[2]); + + // Error + Hint filtering + m_model->setSeverities(IProblem::Error | IProblem::Hint); + QCOMPARE(m_model->rowCount(), 3); + checkSeverityGroup(0, m_problems[0]); + checkSeverityGroup(2, m_problems[2]); + + m_model->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); + // Check if diagnostics get to the right place m_model->clearProblems(); m_model->addProblem(m_diagnosticTestProblem); { QModelIndex parent = m_model->index(0, 0); QVERIFY(parent.isValid()); checkDiagnostics(0, parent); } m_model->clearProblems(); } // Generate 3 problems, all with different paths, different severity // Also generates a problem with diagnostics void TestProblemModel::generateProblems() { IProblem::Ptr p1(new DetectedProblem()); IProblem::Ptr p2(new DetectedProblem()); IProblem::Ptr p3(new DetectedProblem()); DocumentRange r1; r1.document = IndexedString("/just/a/random/path"); p1->setDescription(QStringLiteral("PROBLEM1")); p1->setSeverity(IProblem::Error); p1->setFinalLocation(r1); DocumentRange r2; r2.document = IndexedString("/just/another/path"); p2->setDescription(QStringLiteral("PROBLEM2")); p2->setSeverity(IProblem::Warning); p2->setFinalLocation(r2); DocumentRange r3; r3.document = IndexedString("/yet/another/test/path"); p2->setDescription(QStringLiteral("PROBLEM3")); p3->setSeverity(IProblem::Hint); p3->setFinalLocation(r3); m_problems.push_back(p1); m_problems.push_back(p2); m_problems.push_back(p3); // Problem for diagnostic testing IProblem::Ptr p(new DetectedProblem()); DocumentRange r; r.document = IndexedString("DIAGTEST"); p->setFinalLocation(r); p->setDescription(QStringLiteral("PROBLEM")); p->setSeverity(IProblem::Error); IProblem::Ptr d(new DetectedProblem()); d->setDescription(QStringLiteral("DIAG")); IProblem::Ptr dd(new DetectedProblem()); dd->setDescription(QStringLiteral("DIAGDIAG")); d->addDiagnostic(dd); p->addDiagnostic(d); m_diagnosticTestProblem = p; } bool TestProblemModel::checkIsSame(int row, const QModelIndex &parent, const IProblem::Ptr &problem) { QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } bool TestProblemModel::checkDiagnostics(int row, const QModelIndex &parent) { MYCOMPARE(m_model->rowCount(parent), 1); QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), m_diagnosticTestProblem->description()); QModelIndex diagidx; IProblem::Ptr diag = m_diagnosticTestProblem->diagnostics().at(0); diagidx = m_model->index(0, 0, idx); MYVERIFY(diagidx.isValid()); MYCOMPARE(m_model->data(diagidx).toString(), diag->description()); QModelIndex diagdiagidx; IProblem::Ptr diagdiag = diag->diagnostics().at(0); diagdiagidx = m_model->index(0, 0, diagidx); MYVERIFY(diagdiagidx.isValid()); MYCOMPARE(m_model->data(diagdiagidx).toString(), diagdiag->description()); return true; } bool TestProblemModel::checkDisplay(int row, const QModelIndex &parent, const IProblem::Ptr &problem) { QModelIndex idx; idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); idx = m_model->index(row, 1, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->sourceString()); idx = m_model->index(row, 2, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->finalLocation().document.str()); idx = m_model->index(row, 3, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), QString::number(problem->finalLocation().start().line() + 1)); idx = m_model->index(row, 4, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), QString::number(problem->finalLocation().start().column() + 1)); return true; } bool TestProblemModel::checkLabel(int row, const QModelIndex &parent, const QString &label) { QModelIndex idx = m_model->index(row, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), label); return true; } bool TestProblemModel::checkPathGroup(int row, const IProblem::Ptr &problem) { QModelIndex parent = m_model->index(row, 0); MYVERIFY(parent.isValid()); MYCOMPARE(m_model->data(parent).toString(), problem->finalLocation().document.str()); QModelIndex idx = m_model->index(0, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } bool TestProblemModel::checkSeverityGroup(int row, const IProblem::Ptr &problem) { QModelIndex parent = m_model->index(row, 0); MYVERIFY(parent.isValid()); MYCOMPARE(m_model->data(parent).toString(), problem->severityString()); QModelIndex idx = m_model->index(0, 0, parent); MYVERIFY(idx.isValid()); MYCOMPARE(m_model->data(idx).toString(), problem->description()); return true; } QTEST_MAIN(TestProblemModel) #include "test_problemmodel.moc" diff --git a/shell/tests/test_problemstore.cpp b/shell/tests/test_problemstore.cpp index 945ca61470..5832e45811 100644 --- a/shell/tests/test_problemstore.cpp +++ b/shell/tests/test_problemstore.cpp @@ -1,144 +1,158 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 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 #include #include #include #include #include #include #include using namespace KDevelop; class TestProblemStore : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testAddProblems(); void testClearProblems(); void testSetProblems(); void testFindNode(); void testSeverity(); + void testSeverities(); void testScope(); private: void generateProblems(); QScopedPointer m_store; QVector m_problems; }; void TestProblemStore::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_store.reset(new ProblemStore()); m_store->setScope(CurrentDocument); QVERIFY(m_store->scope() == CurrentDocument); generateProblems(); } void TestProblemStore::cleanupTestCase() { TestCore::shutdown(); } void TestProblemStore::testAddProblems() { QCOMPARE(m_store->count(), 0); int c = 0; foreach (const IProblem::Ptr &problem, m_problems) { m_store->addProblem(problem); c++; QCOMPARE(m_store->count(), c); } } void TestProblemStore::testClearProblems() { m_store->clear(); QCOMPARE(m_store->count(), 0); } void TestProblemStore::testSetProblems() { m_store->setProblems(m_problems); QCOMPARE(m_store->count(), m_problems.count()); } void TestProblemStore::testFindNode() { for (int i = 0; i < m_problems.count(); i++) { const ProblemNode *node = dynamic_cast(m_store->findNode(i)); QVERIFY(node != nullptr); QVERIFY(node->problem().data() != nullptr); QCOMPARE(node->problem().data()->description(), m_problems[i]->description()); } } void TestProblemStore::testSeverity() { IProblem::Severity severity = IProblem::Error; QVERIFY(severity != m_store->severity()); QSignalSpy spy(m_store.data(), &ProblemStore::changed); m_store->setSeverity(severity); QVERIFY(m_store->severity() == severity); QCOMPARE(spy.count(), 1); } +void TestProblemStore::testSeverities() +{ + IProblem::Severities severities = IProblem::Error | IProblem::Hint; + + QVERIFY(severities != m_store->severities()); + + QSignalSpy spy(m_store.data(), &ProblemStore::changed); + m_store->setSeverities(severities); + + QVERIFY(m_store->severities() == severities); + QCOMPARE(spy.count(), 1); +} + void TestProblemStore::testScope() { QSignalSpy spy(m_store.data(), &ProblemStore::changed); ProblemScope scope = AllProjects; m_store->setScope(scope); QVERIFY(m_store->scope() == scope); QCOMPARE(spy.count(), 1); } void TestProblemStore::generateProblems() { for (int i = 0; i < 5; i++) { IProblem::Ptr problem(new DetectedProblem()); problem->setDescription(QStringLiteral("PROBLEM") + QString::number(i + 1)); m_problems.push_back(problem); } } QTEST_MAIN(TestProblemStore) #include "test_problemstore.moc"