diff --git a/kdevplatform/debugger/tests/test_ivariablecontroller.cpp b/kdevplatform/debugger/tests/test_ivariablecontroller.cpp index 570201f33e..ae276c4d02 100644 --- a/kdevplatform/debugger/tests/test_ivariablecontroller.cpp +++ b/kdevplatform/debugger/tests/test_ivariablecontroller.cpp @@ -1,93 +1,93 @@ /* * KDevelop Debugger Support * * Copyright 2016 Aetf * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_ivariablecontroller.h" #include #include #include #include #include #include #include -QTEST_MAIN(KDevelop::TestIVariableController); +QTEST_MAIN(KDevelop::TestIVariableController) using namespace KDevelop; TestIVariableController::TestIVariableController(QObject* parent) : QObject(parent) { } void TestIVariableController::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); m_debugSession = new TestDebugSession(); // Simulate an already started and paused debug session m_debugSession->runToCursor(); } void KDevelop::TestIVariableController::updateRightAfterEnableAutoUpdate_data() { QTest::addColumn("startAt"); QTest::addColumn("switchTo"); QTest::addColumn("jumpTo"); QTest::newRow("jump to somewhere else") << 1 << 0 << 2; QTest::newRow("jump back") << 1 << 0 << 1; } void TestIVariableController::updateRightAfterEnableAutoUpdate() { QFETCH(int, startAt); QFETCH(int, switchTo); QFETCH(int, jumpTo); auto frameStackModel = m_debugSession->frameStackModel(); auto variableController = qobject_cast(m_debugSession->variableController()); if (!variableController) { return; } frameStackModel->setCurrentThread(0); frameStackModel->setCurrentFrame(startAt); int oldCounter = variableController->updatedTimes(); variableController->setAutoUpdate(IVariableController::UpdateNone); frameStackModel->setCurrentFrame(switchTo); // no update variableController->setAutoUpdate(IVariableController::UpdateLocals); // trigger update // switch back to frame we were at before disable auto update frameStackModel->setCurrentFrame(jumpTo); // trigger another update int updatedTimes = variableController->updatedTimes() - oldCounter; QCOMPARE(updatedTimes, 2); } void TestIVariableController::cleanupTestCase() { delete m_debugSession; TestCore::shutdown(); } diff --git a/kdevplatform/interfaces/isourceformatter.cpp b/kdevplatform/interfaces/isourceformatter.cpp index fc97b01310..1cc4ab236c 100644 --- a/kdevplatform/interfaces/isourceformatter.cpp +++ b/kdevplatform/interfaces/isourceformatter.cpp @@ -1,196 +1,196 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "isourceformatter.h" #include #include #include namespace KDevelop { SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) { } SettingsWidget::~SettingsWidget() { } ISourceFormatter::~ISourceFormatter() { } SourceFormatterStyle::SourceFormatterStyle() { -}; +} SourceFormatterStyle::SourceFormatterStyle(const QString &name) : m_usePreview(false) , m_name(name) { } void SourceFormatterStyle::setContent(const QString &content) { m_content = content; } void SourceFormatterStyle::setCaption(const QString &caption) { m_caption = caption; } QString SourceFormatterStyle::content() const { return m_content; } QString SourceFormatterStyle::caption() const { return m_caption; } QString SourceFormatterStyle::name() const { return m_name; } QString SourceFormatterStyle::description() const { return m_description; } void SourceFormatterStyle::setDescription(const QString &desc) { m_description = desc; } bool SourceFormatterStyle::usePreview() const { return m_usePreview; } void SourceFormatterStyle::setUsePreview(bool use) { m_usePreview = use; } void SourceFormatterStyle::setMimeTypes(const SourceFormatterStyle::MimeList& types) { m_mimeTypes = types; } void SourceFormatterStyle::setMimeTypes(const QStringList& types) { foreach ( auto& t, types ) { auto items = t.split(QLatin1Char('|')); if ( items.size() != 2 ) { continue; } m_mimeTypes << MimeHighlightPair{items.at(0), items.at(1)}; } } void SourceFormatterStyle::setOverrideSample(const QString &sample) { m_overrideSample = sample; } QString SourceFormatterStyle::overrideSample() const { return m_overrideSample; } SourceFormatterStyle::MimeList SourceFormatterStyle::mimeTypes() const { return m_mimeTypes; } QVariant SourceFormatterStyle::mimeTypesVariant() const { QStringList result; result.reserve(m_mimeTypes.size()); for ( const auto& item: m_mimeTypes ) { result << item.mimeType + QLatin1Char('|') + item.highlightMode; } return QVariant::fromValue(result); } bool SourceFormatterStyle::supportsLanguage(const QString &language) const { for ( const auto& item: m_mimeTypes ) { if ( item.highlightMode == language ) { return true; } } return false; } QString SourceFormatterStyle::modeForMimetype(const QMimeType& mime) const { foreach (const auto& item, mimeTypes()) { if (mime.inherits(item.mimeType)) { return item.highlightMode; } } return QString(); } void SourceFormatterStyle::copyDataFrom(SourceFormatterStyle *other) { m_content = other->content(); m_mimeTypes = other->mimeTypes(); m_overrideSample = other->overrideSample(); } QString ISourceFormatter::optionMapToString(const QMap &map) { QString options; QMap::const_iterator it = map.constBegin(); for (; it != map.constEnd(); ++it) { options += it.key() + QLatin1Char('=') + it.value().toString() + QLatin1Char(','); } return options; } QMap ISourceFormatter::stringToOptionMap(const QString &options) { QMap map; QStringList pairs = options.split(QLatin1Char(','), QString::SkipEmptyParts); QStringList::const_iterator it; for (it = pairs.constBegin(); it != pairs.constEnd(); ++it) { const QStringList bits = (*it).split(QLatin1Char('=')); map[bits[0]] = bits[1]; } return map; } QString ISourceFormatter::missingExecutableMessage(const QString &name) { return i18n("The executable %1 cannot be found. Please make sure" " it is installed and can be executed.
" "The plugin will not work until you fix this problem.", QLatin1String("") + name + QLatin1String("")); } } // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/kdevplatform/language/backgroundparser/backgroundparser.cpp b/kdevplatform/language/backgroundparser/backgroundparser.cpp index d0cd866430..43f5e39622 100644 --- a/kdevplatform/language/backgroundparser/backgroundparser.cpp +++ b/kdevplatform/language/backgroundparser/backgroundparser.cpp @@ -1,940 +1,940 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * 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 "backgroundparser.h" #include "qtcompat_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parsejob.h" using namespace KDevelop; namespace { const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we do not need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = QStringLiteral("..."); if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || (original.fileName().isEmpty() && original.isLocalFile())) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; inline uint qHash(const DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); -}; +} struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QVector> notifyWhenReady() const { QVector> ret; foreach(const DocumentParseTarget &target, targets) { if(target.notifyWhenReady) ret << target.notifyWhenReady; } return ret; } }; Q_DECLARE_TYPEINFO(DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(DocumentParsePlan, Q_MOVABLE_TYPE); class KDevelop::BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_progressTimer.setSingleShot(true); m_progressTimer.setInterval(500); ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); QObject::connect(&m_progressTimer, &QTimer::timeout, m_parser, &BackgroundParser::updateProgressBar); } void startTimerThreadSafe(int delay) { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection, Q_ARG(int, delay)); } ~BackgroundParserPrivate() { m_weaver.resume(); m_weaver.finish(); } // Non-mutex guarded functions, only call with m_mutex acquired. int currentBestRunningPriority() const { int bestRunningPriority = BackgroundParser::WorstPriority; for (const auto* decorator : m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } return bestRunningPriority; } IndexedString nextDocumentToParse() const { // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. const int bestRunningPriority = currentBestRunningPriority(); for (auto it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { const auto priority = it1.key(); if(priority > m_neededPriority) break; //The priority is not good enough to be processed right now if (m_parseJobs.count() >= m_threads && priority > BackgroundParser::NormalPriority && !specialParseJob) { break; //The additional parsing thread is reserved for higher priority parsing } for (const auto& url : it1.value()) { // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(url)) { continue; } Q_ASSERT(m_documents.contains(url)); const auto& parsePlan = m_documents[url]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { continue; } return url; } } return {}; } /** * Create a single delayed parse job * * E.g. jobs for documents which have been changed by the user, but also to * handle initial startup where we parse all project files. */ void parseDocumentsInternal() { if(m_shuttingDown) return; //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if (m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority)) { return; } const auto& url = nextDocumentToParse(); if (!url.isEmpty()) { qCDebug(LANGUAGE) << "creating parse-job" << url << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(url.str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = nullptr; { // copy shared data before unlocking the mutex const auto parsePlanConstIt = m_documents.constFind(url); const DocumentParsePlan parsePlan = *parsePlanConstIt; // we must not lock the mutex while creating a parse job // this could in turn lock e.g. the DUChain and then // we have a classic lock order inversion (since, usually, // we lock first the duchain and then our background parser // mutex) // see also: https://bugs.kde.org/show_bug.cgi?id=355100 m_mutex.unlock(); decorator = createParseJob(url, parsePlan); m_mutex.lock(); } // iterator might get invalid during the time we didn't have the lock // search again const auto parsePlanIt = m_documents.find(url); if (parsePlanIt != m_documents.end()) { // Remove all mentions of this document. for (const auto& target : qAsConst(parsePlanIt->targets)) { m_documentsForPriority[target.priority].remove(url); } m_documents.erase(parsePlanIt); } else { qCWarning(LANGUAGE) << "Document got removed during parse job creation:" << url; } if (decorator) { if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread m_parseJobs.insert(url, decorator); m_weaver.enqueue(ThreadWeaver::JobPointer(decorator)); } else { --m_maxParseJobs; } if (!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); } else { // make sure we cleaned up properly // TODO: also empty m_documentsForPriority when m_documents is empty? or do we want to keep capacity? Q_ASSERT(std::none_of(m_documentsForPriority.constBegin(), m_documentsForPriority.constEnd(), [] (const QSet& docs) { return !docs.isEmpty(); })); } } m_parser->updateProgressData(); } // NOTE: you must not access any of the data structures that are protected by any of the // background parser internal mutexes in this method // see also: https://bugs.kde.org/show_bug.cgi?id=355100 ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, const DocumentParsePlan& parsePlan) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); const auto languages = m_languageController->languagesForUrl(qUrl); const auto& notifyWhenReady = parsePlan.notifyWhenReady(); for (const auto language : languages) { if (!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(parsePlan.priority()); job->setMinimumFeatures(parsePlan.features()); job->setNotifyWhenReady(notifyWhenReady); job->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if (languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed for (const auto& n : notifyWhenReady) { if (!n) { continue; } QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); } return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; if (qEnvironmentVariableIsSet("KDEV_BACKGROUNDPARSER_MAXTHREADS")) { m_parser->setThreadCount(qEnvironmentVariableIntValue("KDEV_BACKGROUNDPARSER_MAXTHREADS")); } else { m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", QThread::idealThreadCount())); } resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } bool isSuspended() const { return m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; } void suspend() { qCDebug(LANGUAGE) << "Suspending background parser"; if (isSuspended()) { // Already suspending qCWarning(LANGUAGE) << "Already suspended or suspending"; return; } m_timer.stop(); m_weaver.suspend(); } void resume() { qCDebug(LANGUAGE) << "Resuming background parser"; if (m_timer.isActive() && !isSuspended()) { // Not suspended qCWarning(LANGUAGE) << "Not suspended"; return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay = 500; int m_threads = 1; bool m_shuttingDown; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; // generic high-level mutex QMutex m_mutex; // local mutex only protecting m_managed QMutex m_managedMutex; // A change tracker for each managed document QHash m_managed; int m_maxParseJobs = 0; int m_doneParseJobs = 0; QHash m_jobProgress; /// The minimum priority needed for processed jobs int m_neededPriority = BackgroundParser::WorstPriority; int m_progressMax = 0; int m_progressDone = 0; QTimer m_progressTimer; }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressData(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for (auto it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags, int delay) { qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); auto it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } if ( delay == ILanguageSupport::DefaultDelay ) { delay = d->m_delay; } d->startTimerThreadSafe(delay); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (d->isSuspended() || !d->m_loadingProjects.empty()) { startTimer(d->m_delay); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressData(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(d->m_delay); } void BackgroundParser::abortAllJobs() { qCDebug(LANGUAGE) << "Aborting all parse jobs"; d->m_weaver.requestAbort(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressData(); } void BackgroundParser::updateProgressData() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for (auto it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) { additionalProgress += *it; } d->m_progressMax = d->m_maxParseJobs*1000; d->m_progressDone = (additionalProgress + d->m_doneParseJobs)*1000; if (!d->m_progressTimer.isActive()) { d->m_progressTimer.start(); } } // Cancel progress updating and hide progress-bar when parsing is done. if(d->m_doneParseJobs == d->m_maxParseJobs || (d->m_neededPriority == BackgroundParser::BestPriority && d->m_weaver.queueLength() == 0)) { if (d->m_progressTimer.isActive()) { d->m_progressTimer.stop(); } emit d->m_parser->hideProgress(d->m_parser); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int milliseconds) { if (d->m_delay != milliseconds) { d->m_delay = milliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_managedMutex); return d->m_managed.keys(); } bool BackgroundParser::waitForIdle() const { QList runningParseJobsUrls; forever { { QMutexLocker lock(&d->m_mutex); if (d->m_parseJobs.isEmpty()) { qCDebug(LANGUAGE) << "All parse jobs done" << d->m_parseJobs.keys(); return true; } if (d->m_parseJobs.size() != runningParseJobsUrls.size()) { runningParseJobsUrls = d->m_parseJobs.keys(); qCDebug(LANGUAGE) << "Waiting for background parser to get in idle state... -- the following parse jobs are still running:" << runningParseJobsUrls; } } QCoreApplication::processEvents(); QThread::msleep(100); } return false; } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return nullptr; } if ( !isValidURL(url) ) { qCWarning(LANGUAGE) << "Tracker requested for invalild URL:" << url.toUrl(); } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_managedMutex); return d->m_managed.value(url, nullptr); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); QMutexLocker l2(&d->m_managedMutex); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this QMutexLocker l2(&d->m_managedMutex); if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !trackerForUrl(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer(int delay) { if (!d->isSuspended()) { d->m_timer.start(delay); } } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::updateProgressBar() { emit showProgress(this, 0, d->m_progressMax, d->m_progressDone); } diff --git a/kdevplatform/language/codecompletion/codecompletioncontext.h b/kdevplatform/language/codecompletion/codecompletioncontext.h index 817866fa31..0ff0069803 100644 --- a/kdevplatform/language/codecompletion/codecompletioncontext.h +++ b/kdevplatform/language/codecompletion/codecompletioncontext.h @@ -1,111 +1,111 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODECOMPLETIONCONTEXT_H #define KDEVPLATFORM_CODECOMPLETIONCONTEXT_H #include "../duchain/duchainpointer.h" #include #include "../editor/cursorinrevision.h" #include "codecompletionitem.h" namespace KTextEditor { class View; class Cursor; } namespace KDevelop { class CursorInRevision; class CompletionTreeItem; class CompletionTreeElement; typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; /** * This class is responsible for finding out what kind of completion is needed, what expression should be evaluated for the container-class of the completion, what conversion will be applied to the result of the completion, etc. * */ class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletionContext : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; /** * @param text the text to analyze. It usually is the text in the range starting at the beginning of the context, * and ending at the position where completion should start * * @warning The du-chain must be unlocked when this is called * */ CodeCompletionContext(const KDevelop::DUContextPointer& context, const QString& text, const KDevelop::CursorInRevision& position, int depth = 0); virtual ~CodeCompletionContext(); /** * @return Whether this context is valid for code-completion */ bool isValid() const; /** * @return Depth of the context. The basic completion-context has depth 0, its parent 1, and so on.. */ int depth() const; /** * Computes the full set of completion items, using the information retrieved earlier. * Should only be called on the first context, parent contexts are included in the computations. * * @param abort Checked regularly, and if false, the computation is aborted. * * @warning Please check @p abort and @p isValid when reimplementing this method */ virtual QList completionItems(bool& abort, bool fullCompletion = true) = 0; /** * After completionItems(..) has been called, this may return completion-elements that are already grouped, * for example using custom grouping(@see CompletionCustomGroupNode */ virtual QList ungroupedElements(); /** * In the case of recursive argument-hints, there may be a chain of parent-contexts, each for the higher argument-matching * The parentContext() should always have the access-operation FunctionCallAccess. * When a completion-list is computed, the members of the list can be highlighted that match * the corresponding parentContext()->functions() function-argument, or parentContext()->additionalMatchTypes() */ CodeCompletionContext* parentContext(); ///Sets the new parent context, and also updates the depth void setParentContext(QExplicitlySharedDataPointer newParent); DUContext* duContext() const; protected: static QString extractLastLine(const QString& str); QString m_text; int m_depth; bool m_valid; KDevelop::CursorInRevision m_position; KDevelop::DUContextPointer m_duContext; QExplicitlySharedDataPointer m_parentContext; }; } -Q_DECLARE_METATYPE(KDevelop::CodeCompletionContext::Ptr); +Q_DECLARE_METATYPE(KDevelop::CodeCompletionContext::Ptr) #endif diff --git a/kdevplatform/language/codecompletion/codecompletionitem.h b/kdevplatform/language/codecompletion/codecompletionitem.h index 3d35598a78..89bb7e75c9 100644 --- a/kdevplatform/language/codecompletion/codecompletionitem.h +++ b/kdevplatform/language/codecompletion/codecompletionitem.h @@ -1,150 +1,150 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2007-2008 David Nolden * * 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_KDEV_CODECOMPLETIONITEM_H #define KDEVPLATFORM_KDEV_CODECOMPLETIONITEM_H #include #include "../duchain/duchainpointer.h" namespace KTextEditor { class CodeCompletionModel; class Range; class Cursor; } class QModelIndex; namespace KDevelop { class CodeCompletionModel; struct CompletionTreeNode; class CompletionTreeItem; class IndexedType; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeElement : public QSharedData { public: CompletionTreeElement(); virtual ~CompletionTreeElement(); CompletionTreeElement* parent() const; /// Reparenting is not supported. This is only allowed if parent() is still zero. void setParent(CompletionTreeElement*); int rowInParent() const; int columnInParent() const; /// Each element is either a node, or an item. CompletionTreeNode* asNode(); CompletionTreeItem* asItem(); template T* asItem() { return dynamic_cast(this); } template const T* asItem() const { return dynamic_cast(this); } const CompletionTreeNode* asNode() const; const CompletionTreeItem* asItem() const; private: CompletionTreeElement* m_parent; int m_rowInParent; }; struct KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeNode : public CompletionTreeElement { CompletionTreeNode(); ~CompletionTreeNode() override; KTextEditor::CodeCompletionModel::ExtraItemDataRoles role; QVariant roleValue; /// Will append the child, and initialize it correctly to create a working tree-structure void appendChild(QExplicitlySharedDataPointer); void appendChildren(QList >); void appendChildren(QList >); /// @warning Do not manipulate this directly, that's bad for consistency. Use appendChild instead. QList > children; }; class KDEVPLATFORMLANGUAGE_EXPORT CompletionTreeItem : public CompletionTreeElement { public: /// Execute the completion item. The default implementation does nothing. virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word); /// Should return normal completion data, @see KTextEditor::CodeCompletionModel /// The default implementation returns "unimplemented", so re-implement it! /// The duchain is not locked when this is called virtual QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const; /// Should return the inheritance-depth. The completion-items don't need to return it through the data() function. virtual int inheritanceDepth() const; /// Should return the argument-hint depth. The completion-items don't need to return it through the data() function. virtual int argumentHintDepth() const; /// The default-implementation calls DUChainUtils::completionProperties virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const; /// If this item represents a Declaration, this should return the declaration. /// The default-implementation returns zero. virtual DeclarationPointer declaration() const; /// Should return the types should be used for matching items against this one when it's an argument hint. /// The matching against all types should be done, and the best one will be used as final match result. virtual QList typeForArgumentMatching() const; /// Should return whether this completion-items data changes with input done by the user during code-completion. /// Returning true is very expensive. virtual bool dataChangedWithInput() const; }; /// A custom-group node, that can be used as-is. Just create it, and call appendChild to add group items. /// The items in the group will be shown in the completion-list with a group-header that contains the given name struct KDEVPLATFORMLANGUAGE_EXPORT CompletionCustomGroupNode : public CompletionTreeNode { /// @param inheritanceDepth See KTextEditor::CodeCompletionModel::GroupRole explicit CompletionCustomGroupNode(const QString& groupName, int inheritanceDepth = 700); int inheritanceDepth; }; typedef QExplicitlySharedDataPointer CompletionTreeItemPointer; typedef QExplicitlySharedDataPointer CompletionTreeElementPointer; } -Q_DECLARE_METATYPE(KDevelop::CompletionTreeElementPointer); +Q_DECLARE_METATYPE(KDevelop::CompletionTreeElementPointer) #endif diff --git a/kdevplatform/language/codegen/tests/test_documentchangeset.cpp b/kdevplatform/language/codegen/tests/test_documentchangeset.cpp index e853b713dd..b31bb56dfe 100644 --- a/kdevplatform/language/codegen/tests/test_documentchangeset.cpp +++ b/kdevplatform/language/codegen/tests/test_documentchangeset.cpp @@ -1,74 +1,74 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_documentchangeset.h" #include #include #include #include #include -QTEST_GUILESS_MAIN(TestDocumentchangeset); +QTEST_GUILESS_MAIN(TestDocumentchangeset) using namespace KDevelop; void TestDocumentchangeset::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestDocumentchangeset::cleanupTestCase() { TestCore::shutdown(); } void TestDocumentchangeset::testReplaceSameLine() { TestFile file(QStringLiteral("abc def ghi"), QStringLiteral("cpp")); qDebug() << file.fileContents(); DocumentChangeSet changes; changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 0, 0, 3), QStringLiteral("abc"), QStringLiteral("foobar") )); changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 4, 0, 7), QStringLiteral("def"), QStringLiteral("foobar") )); changes.addChange( DocumentChange( file.url(), KTextEditor::Range(0, 8, 0, 11), QStringLiteral("ghi"), QStringLiteral("foobar") )); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); qDebug() << result.m_failureReason << result.m_success; QVERIFY(result); } diff --git a/kdevplatform/language/duchain/duchainbase.cpp b/kdevplatform/language/duchain/duchainbase.cpp index 62af825f85..017cfb06c5 100644 --- a/kdevplatform/language/duchain/duchainbase.cpp +++ b/kdevplatform/language/duchain/duchainbase.cpp @@ -1,237 +1,237 @@ /* This is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007/2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchainbase.h" #include #include #include "duchainpointer.h" #include "parsingenvironment.h" #include #include "topducontext.h" #include "duchainregister.h" #include #include #include #include #include namespace KDevelop { REGISTER_DUCHAIN_ITEM(DUChainBase); uint DUChainBaseData::classSize() const { return DUChainItemSystem::self().dataClassSize(*this); } DUChainBase::DUChainBase(const RangeInRevision& range) : d_ptr(new DUChainBaseData) { d_func_dynamic()->m_range = range; d_func_dynamic()->setClassId(this); } DUChainBase::DUChainBase( DUChainBaseData & dd, const RangeInRevision& range ) : d_ptr( &dd ) { d_func_dynamic()->m_range = range; } DUChainBase::DUChainBase( DUChainBaseData & dd ) : d_ptr( &dd ) { } DUChainBase::DUChainBase( DUChainBase& rhs ) : d_ptr( new DUChainBaseData(*rhs.d_func()) ) { d_func_dynamic()->setClassId(this); } IndexedString DUChainBase::url() const { TopDUContext* top = topContext(); if(top) return top->TopDUContext::url(); else return IndexedString(); } void DUChainBase::setData(DUChainBaseData* data, bool constructorCalled) { Q_ASSERT(data); Q_ASSERT(d_ptr); if(constructorCalled) KDevelop::DUChainItemSystem::self().callDestructor(static_cast(d_ptr)); if(d_ptr->m_dynamic) // If the data object isn't dynamic, then it is part of a central repository, and cannot be deleted here. delete d_ptr; d_ptr = data; } DUChainBase::~DUChainBase() { if (m_ptr) m_ptr->m_base = nullptr; if(d_ptr->m_dynamic) { KDevelop::DUChainItemSystem::self().callDestructor(d_ptr); delete d_ptr; d_ptr = nullptr; } } TopDUContext* DUChainBase::topContext() const { ///@todo Move the reference to the top-context right into this class, as it's common to all inheriters return nullptr; } namespace { QMutex weakPointerMutex; -}; +} const QExplicitlySharedDataPointer& DUChainBase::weakPointer() const { if (!m_ptr) { QMutexLocker lock(&weakPointerMutex); // The mutex is used to make sure we don't create m_ptr twice at the same time m_ptr = new DUChainPointerData(const_cast(this)); m_ptr->m_base = const_cast(this); } return m_ptr; } void DUChainBase::rebuildDynamicData(DUContext* parent, uint ownIndex) { Q_UNUSED(parent) Q_UNUSED(ownIndex) } void DUChainBase::makeDynamic() { Q_ASSERT(d_ptr); if(!d_func()->m_dynamic) { Q_ASSERT(d_func()->classId); DUChainBaseData* newData = DUChainItemSystem::self().cloneData(*d_func()); enableDUChainReferenceCounting(d_ptr, DUChainItemSystem::self().dynamicSize(*static_cast(d_ptr))); //We don't delete the previous data, because it's embedded in the top-context when it isn't dynamic. //However we do call the destructor, to keep semantic stuff like reference-counting within the data class working correctly. KDevelop::DUChainItemSystem::self().callDestructor(static_cast(d_ptr)); disableDUChainReferenceCounting(d_ptr); d_ptr = newData; Q_ASSERT(d_ptr); Q_ASSERT(d_func()->m_dynamic); Q_ASSERT(d_func()->classId); } } RangeInRevision DUChainBase::range() const { return d_func()->m_range; } KTextEditor::Range DUChainBase::rangeInCurrentRevision() const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(d_func()->m_range, revision); } // If the document is not open, we can simply cast the range over, as no translation can be done return d_func()->m_range.castToSimpleRange(); } PersistentMovingRange::Ptr DUChainBase::createRangeMoving() const { VERIFY_FOREGROUND_LOCKED return PersistentMovingRange::Ptr(new PersistentMovingRange(rangeInCurrentRevision(), url())); } CursorInRevision DUChainBase::transformToLocalRevision(const KTextEditor::Cursor& cursor) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToRevision(cursor, revision); } return CursorInRevision::castFromSimpleCursor(cursor); } RangeInRevision DUChainBase::transformToLocalRevision(const KTextEditor::Range& range) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToRevision(range, revision); } return RangeInRevision::castFromSimpleRange(range); } KTextEditor::Range DUChainBase::transformFromLocalRevision(const KDevelop::RangeInRevision& range) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(range, revision); } return range.castToSimpleRange(); } KTextEditor::Cursor DUChainBase::transformFromLocalRevision(const KDevelop::CursorInRevision& cursor) const { DocumentChangeTracker* tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(url()); if(tracker && topContext() && topContext()->parsingEnvironmentFile()) { qint64 revision = topContext()->parsingEnvironmentFile()->modificationRevision().revision; return tracker->transformToCurrentRevision(cursor, revision); } return cursor.castToSimpleCursor(); } void DUChainBase::setRange(const RangeInRevision& range) { d_func_dynamic()->m_range = range; } QThreadStorage shouldCreateConstantDataStorage; bool& DUChainBaseData::shouldCreateConstantData() { return shouldCreateConstantDataStorage.localData(); } } diff --git a/kdevplatform/language/duchain/tests/bench_hashes.cpp b/kdevplatform/language/duchain/tests/bench_hashes.cpp index 808ed41516..9a1e92cc22 100644 --- a/kdevplatform/language/duchain/tests/bench_hashes.cpp +++ b/kdevplatform/language/duchain/tests/bench_hashes.cpp @@ -1,315 +1,315 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * * 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 "bench_hashes.h" #include #include #include #include #include #include #include // similar to e.g. modificationrevision.cpp struct DataT { QDateTime a; QDateTime b; }; typedef QPair DataPair; typedef QVector InputData; struct IndexedStringHash { inline uint operator() (const KDevelop::IndexedString& str) const { return str.hash(); } }; typedef std::unordered_map StlHash; inline void insertData(StlHash& hash, const InputData& data) { foreach(const DataPair& pair, data) { hash.insert(std::make_pair(pair.first, pair.second)); } } typedef QHash QStringHash; inline void insertData(QStringHash& hash, const InputData& data) { foreach(const DataPair& pair, data) { hash.insert(pair.first, pair.second); } } -QTEST_GUILESS_MAIN(BenchHashes); +QTEST_GUILESS_MAIN(BenchHashes) using namespace KDevelop; Q_DECLARE_METATYPE(InputData) void BenchHashes::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); } void BenchHashes::cleanupTestCase() { TestCore::shutdown(); } void BenchHashes::feedData() { QTest::addColumn("useStl"); QTest::addColumn("data"); InputData data; QVector sizes = QVector() << 100 << 1000 << 10000 << 100000; foreach(int size, sizes) { for(int i = data.size(); i < size; ++i) { data << qMakePair(IndexedString(QString::number(i)), DataT()); } QCOMPARE(data.size(), size); QTest::newRow(qPrintable(QStringLiteral("unordered_map-%1").arg(size))) << true << data; QTest::newRow(qPrintable(QStringLiteral("qhash-%1").arg(size))) << false << data; } } void BenchHashes::insert() { QFETCH(bool, useStl); QFETCH(InputData, data); if (useStl) { QBENCHMARK { StlHash hash; insertData(hash, data); } } else { QBENCHMARK { QStringHash hash; insertData(hash, data); } } } void BenchHashes::insert_data() { feedData(); } void BenchHashes::find() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.find(pair.first); } } } } void BenchHashes::find_data() { feedData(); } void BenchHashes::constFind() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); const StlHash& constHash = hash; QBENCHMARK { foreach(const DataPair& pair, data) { (void) constHash.find(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { (void) hash.constFind(pair.first); } } } } void BenchHashes::constFind_data() { feedData(); } void BenchHashes::remove() { QFETCH(bool, useStl); QFETCH(InputData, data); if(useStl) { StlHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { hash.erase(pair.first); } } } else { QStringHash hash; insertData(hash, data); QBENCHMARK { foreach(const DataPair& pair, data) { hash.remove(pair.first); } } } } void BenchHashes::remove_data() { feedData(); } struct TypeRepoTestData { size_t size; void* ptr; }; /** * somewhat artificial benchmark to test speed impact if we'd ever change * the underlying data type of the TypeSystem / TypeRegister. */ void BenchHashes::typeRepo() { QFETCH(int, type); if (type == 1 || type == 2) { QVector v; for(int i = 0; i < 100; ++i) { v.append(new TypeRepoTestData); } if (type == 1) { QBENCHMARK { for(int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 2) { TypeRepoTestData** a = v.data(); QBENCHMARK { for(int i = 0; i < 100; ++i) { a[i]->size++; } } } } else if (type == 3) { QHash v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 4) { QMap v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.value(i)->size++; } } } else if (type == 5) { std::unordered_map v; for(int i = 0; i < 100; ++i) { v[i] = new TypeRepoTestData; } QBENCHMARK { for(int i = 0; i < 100; ++i) { v.at(i)->size++; } } } else if (type == 6) { // for the idea, look at c++'s lexer.cpp const int vectors = 5; typedef QPair Pair; typedef QVarLengthArray InnerVector; QVarLengthArray v; v.resize(vectors); for(int i = 0; i < 100; ++i) { v[i % vectors] << qMakePair(i, new TypeRepoTestData); } QBENCHMARK { for(int i = 0; i < 100; ++i) { foreach(const Pair& p, v.at(i % vectors)) { if (p.first == i) { p.second->size++; break; } } } } } else if (type == 0) { QBENCHMARK {} } } void BenchHashes::typeRepo_data() { QTest::addColumn("type"); QTest::newRow("noop") << 0; QTest::newRow("vector") << 1; QTest::newRow("vector-raw") << 2; QTest::newRow("qhash") << 3; QTest::newRow("qmap") << 4; QTest::newRow("unordered_map") << 5; QTest::newRow("nested-vector") << 6; } diff --git a/kdevplatform/language/duchain/tests/test_identifier.cpp b/kdevplatform/language/duchain/tests/test_identifier.cpp index 21bc7c0183..12c35e961d 100644 --- a/kdevplatform/language/duchain/tests/test_identifier.cpp +++ b/kdevplatform/language/duchain/tests/test_identifier.cpp @@ -1,215 +1,215 @@ /* * This file is part of KDevelop * Copyright 2012-2013 Milian Wolff * * 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 "test_identifier.h" #include #include #include #include #include #include -QTEST_GUILESS_MAIN(TestIdentifier); +QTEST_GUILESS_MAIN(TestIdentifier) using namespace KDevelop; void TestIdentifier::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestIdentifier::cleanupTestCase() { TestCore::shutdown(); } void TestIdentifier::testIdentifier() { QFETCH(QString, stringId); const IndexedString indexedStringId(stringId); Identifier id(stringId); QCOMPARE(id.isEmpty(), stringId.isEmpty()); QCOMPARE(id, Identifier(stringId)); QVERIFY(!(id != Identifier(stringId))); QCOMPARE(id, Identifier(stringId)); QCOMPARE(id, Identifier(indexedStringId)); QCOMPARE(id.identifier(), indexedStringId); QCOMPARE(id.toString(), stringId); QVERIFY(id.nameEquals(Identifier(stringId))); QVERIFY(!id.isUnique()); if (stringId.isEmpty()) { QVERIFY(id.inRepository()); QVERIFY(Identifier(id).inRepository()); QVERIFY(Identifier(indexedStringId).inRepository()); } Identifier copy = id; QCOMPARE(copy, id); copy = copy; QCOMPARE(copy, id); copy = Identifier(); QVERIFY(copy.isEmpty()); copy = id; QCOMPARE(copy, id); IndexedIdentifier indexedId(id); QVERIFY(indexedId == id); QCOMPARE(indexedId, IndexedIdentifier(id)); QCOMPARE(indexedId.isEmpty(), stringId.isEmpty()); QCOMPARE(indexedId.identifier(), id); IndexedIdentifier indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); indexedCopy = indexedCopy; QCOMPARE(indexedCopy, indexedId); indexedCopy = IndexedIdentifier(); QVERIFY(indexedCopy.isEmpty()); indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); Identifier moved = std::move(id); QVERIFY(id.isEmpty()); QVERIFY(id.inRepository()); QCOMPARE(moved, copy); IndexedIdentifier movedIndexed = std::move(indexedId); QVERIFY(indexedId.isEmpty()); QCOMPARE(movedIndexed, indexedCopy); } void TestIdentifier::testIdentifier_data() { QTest::addColumn("stringId"); QTest::newRow("empty") << QString(); QTest::newRow("foo") << QStringLiteral("foo"); QTest::newRow("bar") << QStringLiteral("bar"); //TODO: test template identifiers } void TestIdentifier::testQualifiedIdentifier() { QFETCH(QString, stringId); const QStringList list = stringId.split(QStringLiteral("::"), QString::SkipEmptyParts); QualifiedIdentifier id(stringId); QCOMPARE(id.isEmpty(), stringId.isEmpty()); QCOMPARE(id, QualifiedIdentifier(stringId)); QVERIFY(!(id != QualifiedIdentifier(stringId))); QCOMPARE(id, QualifiedIdentifier(stringId)); if (list.size() == 1) { QCOMPARE(id, QualifiedIdentifier(Identifier(list.last()))); } else if (list.isEmpty()) { QualifiedIdentifier empty{Identifier()}; QCOMPARE(id, empty); QVERIFY(empty.isEmpty()); QVERIFY(empty.inRepository()); } QCOMPARE(id.toString(), stringId); QCOMPARE(id.toStringList(), list); QCOMPARE(id.top(), Identifier(list.isEmpty() ? QString() : list.last())); if (stringId.isEmpty()) { QVERIFY(id.inRepository()); QVERIFY(QualifiedIdentifier(id).inRepository()); } QualifiedIdentifier copy = id; QCOMPARE(copy, id); copy = copy; QCOMPARE(copy, id); copy = QualifiedIdentifier(); QVERIFY(copy.isEmpty()); copy = id; QCOMPARE(copy, id); IndexedQualifiedIdentifier indexedId(id); QVERIFY(indexedId == id); QCOMPARE(indexedId, IndexedQualifiedIdentifier(id)); QCOMPARE(indexedId.isValid(), !stringId.isEmpty()); QCOMPARE(indexedId.identifier(), id); IndexedQualifiedIdentifier indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); indexedCopy = indexedCopy; QCOMPARE(indexedCopy, indexedId); indexedCopy = IndexedQualifiedIdentifier(); QVERIFY(!indexedCopy.isValid()); indexedCopy = indexedId; QCOMPARE(indexedCopy, indexedId); QualifiedIdentifier moved = std::move(id); QVERIFY(id.isEmpty()); QCOMPARE(moved, copy); IndexedQualifiedIdentifier movedIndexed = std::move(indexedId); QVERIFY(indexedId.isEmpty()); QCOMPARE(movedIndexed, indexedCopy); copy.clear(); QVERIFY(copy.isEmpty()); copy.push(moved); QCOMPARE(copy, moved); copy.push(Identifier(QStringLiteral("lalala"))); QCOMPARE(copy.count(), moved.count() + 1); } void TestIdentifier::testQualifiedIdentifier_data() { QTest::addColumn("stringId"); QTest::newRow("empty") << QString(); QTest::newRow("foo") << "foo"; QTest::newRow("foo::bar") << "foo::bar"; //TODO: test template identifiers } void TestIdentifier::benchIdentifierCopyConstant() { QBENCHMARK { Identifier identifier(QStringLiteral("Asdf")); identifier.index(); Identifier copy(identifier); } } void TestIdentifier::benchIdentifierCopyDynamic() { QBENCHMARK { Identifier identifier(QStringLiteral("Asdf")); Identifier copy(identifier); } } void TestIdentifier::benchQidCopyPush() { QBENCHMARK { Identifier id(QStringLiteral("foo")); QualifiedIdentifier base(id); QualifiedIdentifier copy(base); copy.push(id); } } diff --git a/kdevplatform/language/duchain/types/containertypes.cpp b/kdevplatform/language/duchain/types/containertypes.cpp index 172ff48386..ac9fced31c 100644 --- a/kdevplatform/language/duchain/types/containertypes.cpp +++ b/kdevplatform/language/duchain/types/containertypes.cpp @@ -1,150 +1,150 @@ /* * This file is part of KDevelop * Copyright (C) 2011-2014 Sven Brauch * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this program. If not, see . */ #include "containertypes.h" #include "typeutils.h" #include "typeregister.h" #include "../duchain.h" #include "../duchainlock.h" #include namespace KDevelop { REGISTER_TYPE(ListType); REGISTER_TYPE(MapType); ListType::ListType() : KDevelop::StructureType(createData()) { } ListType::ListType(StructureTypeData& data) : KDevelop::StructureType(data) { } ListType::ListType(const ListType& rhs) : KDevelop::StructureType(copyData(*rhs.d_func())) { } MapType::MapType() -: ListType(createData()) { }; +: ListType(createData()) { } MapType::MapType(ListTypeData& data) -: ListType(data) { }; +: ListType(data) { } MapType::MapType(const MapType& rhs) : ListType(copyData(*rhs.d_func())) { } void ListType::replaceContentType(const AbstractType::Ptr& newType) { d_func_dynamic()->m_contentType = IndexedType(newType); } void MapType::replaceKeyType(const AbstractType::Ptr& newType) { d_func_dynamic()->m_keyType = IndexedType(newType); } IndexedType ListType::contentType() const { return d_func()->m_contentType; } IndexedType MapType::keyType() const { return d_func()->m_keyType; } AbstractType* ListType::clone() const { return new ListType(*this); } AbstractType* MapType::clone() const { return new MapType(*this); } QString ListType::toString() const { QString prefix = KDevelop::StructureType::toString(); auto content = contentType().abstractType(); if ( content ) { return i18n("%1 of %2", prefix, content->toString()); } return prefix; } QString MapType::toString() const { QString prefix = KDevelop::StructureType::toString(); auto content = contentType().abstractType(); auto key = keyType().abstractType(); auto key_str = ( key ? key->toString() : i18n("unknown") ); auto content_str = ( content ? content->toString() : i18n("unknown") ); if ( key || content ) { return i18n("%1 of %2 : %3", prefix, key_str, content_str); } return prefix; } QString ListType::containerToString() const { return KDevelop::StructureType::toString(); } bool ListType::equals(const AbstractType* rhs) const { if ( this == rhs ) { return true; } if ( ! KDevelop::StructureType::equals(rhs) ) { return false; } auto c = dynamic_cast(rhs); if ( ! c ) { return false; } if ( c->contentType() != d_func()->m_contentType ) { return false; } return true; } bool MapType::equals(const AbstractType* rhs) const { if ( ! ListType::equals(rhs) ) { return false; } auto c = dynamic_cast(rhs); return c && c->keyType() == d_func()->m_keyType; } uint ListType::hash() const { return StructureType::hash() + ( contentType().abstractType() ? contentType().abstractType()->hash() : 1 ); } uint MapType::hash() const { return ListType::hash() + ( keyType().abstractType() ? keyType().abstractType()->hash() : 1 ); } } // namespace KDevelop // kate: space-indent on; indent-width 4 diff --git a/kdevplatform/outputview/ifilterstrategy.h b/kdevplatform/outputview/ifilterstrategy.h index 87dab1ba82..a061735d80 100644 --- a/kdevplatform/outputview/ifilterstrategy.h +++ b/kdevplatform/outputview/ifilterstrategy.h @@ -1,89 +1,89 @@ /* Interface description for KDevelop OutputView Filter strategies Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com Copyright (C) 2016 Kevin Funk This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KDEVPLATFORM_IFILTERSTRATEGY_H #define KDEVPLATFORM_IFILTERSTRATEGY_H #include "outputviewexport.h" #include #include namespace KDevelop { struct FilteredItem; /** * Interface class for filtering output. Filtered output is divided into two catagories: Errors * and Actions. Use this interface if you want to write a filter for the outputview. * * @author Morten Danielsen Volden */ class KDEVPLATFORMOUTPUTVIEW_EXPORT IFilterStrategy { public: IFilterStrategy(); virtual ~IFilterStrategy(); struct Progress { Progress(const QString& status = QString(), int percent = -1) : status(status), percent(percent) {} QString status; /// Status message (example: "Building foo.cpp") int percent; /// Percentage from 0-100; -1 indicates no progress could be parsed }; /** * Examine if a given line contains output that is defined as an error (E.g. from a script or from a compiler, or other). * @param line the line to examine * @return FilteredItem with associated metadata if an error is found, an item of type InvalidItem otherwise **/ virtual FilteredItem errorInLine(QString const& line) = 0; /** * Examine if a given line contains output that is defined as an action (E.g. from a script or from a compiler, or other). * @param line the line to examine * @return Filtered of type ActionItem with associated metadata if an action is found, an item of type InvalidItem otherwise **/ virtual FilteredItem actionInLine(QString const& line) = 0; /** * Examine if a given line contains output which reports progress information * * E.g. `make` reports progress like this: * @code * [ 5%] Doing something * [ 6%] Doing something * @endcode * * @return Processed percent & status of the output, default implementation returns default-constructed value */ virtual Progress progressInLine(const QString& line); }; } // namespace KDevelop Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*) -Q_DECLARE_METATYPE(KDevelop::IFilterStrategy::Progress); +Q_DECLARE_METATYPE(KDevelop::IFilterStrategy::Progress) #endif // KDEVPLATFORM_IFILTERSTRATEGY_H diff --git a/kdevplatform/outputview/outputexecutejob.h b/kdevplatform/outputview/outputexecutejob.h index e750be7d8e..88a25b2b71 100644 --- a/kdevplatform/outputview/outputexecutejob.h +++ b/kdevplatform/outputview/outputexecutejob.h @@ -1,257 +1,257 @@ /* This file is part of KDevelop C opyright 2012 Ivan Shapoval*ov This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_OUTPUTEXECUTEJOB_H #define KDEVPLATFORM_OUTPUTEXECUTEJOB_H #include "outputjob.h" #include "outputmodel.h" #include #include namespace KDevelop { class KDEVPLATFORMOUTPUTVIEW_EXPORT OutputExecuteJob : public OutputJob { Q_OBJECT public: enum JobStatus { JobRunning = 0, /**< The job is running */ JobSucceeded = 1, /**< The job has succeeded */ JobCanceled = 2, /**< The job has been cancelled */ JobFailed = 3, /**< The job has failed */ JobNotStarted = 4 /**< The job hasn't been started so far */ }; enum { InvalidWorkingDirectoryError = OutputJob::UserDefinedError, UserDefinedError }; enum JobProperty { AppendProcessString = 0x001, /**< Whether to append a process string to the user-specified job name */ NeedWorkingDirectory = 0x002, /**< Whether to require a non-empty working directory to be provided */ CheckWorkingDirectory = 0x004, /**< Whether to check that the working directory actually exists (and not to create it if needed) */ PortableMessages = 0x008, /**< Whether to set LC_MESSAGES=C in the process' environment */ DisplayStdout = 0x010, /**< Whether to pass process' stdout to the output model */ DisplayStderr = 0x020, /**< Whether to pass process' stderr to the output model */ NoSilentOutput = 0x040, /**< Whether to call \ref startOutput() only if verbosity is \ref OutputJob::Verbose */ PostProcessOutput = 0x080, /**< Whether to connect line maker's signals to \ref postProcessStdout() and \ref postProcessStderr() */ IsBuilderHint = 0x100, /**< Whether to use builder-specific messages to talk to user (e. g. "build directory" instead of "working directory" */ }; Q_FLAGS(JobProperty JobProperties) Q_DECLARE_FLAGS(JobProperties, JobProperty) explicit OutputExecuteJob( QObject* parent = nullptr, OutputJobVerbosity verbosity = OutputJob::Verbose ); ~OutputExecuteJob() override; /** * Get the job's status (associated with the process). * * @returns The job's status. * @see JobStatus */ JobStatus status() const; /** * Get the job's output model. * * @returns The job's output model, downcasted to \ref OutputModel */ OutputModel* model() const; /** * Returns a working directory for the job's process. * * @returns URL which has been set through \ref setWorkingDirectory(); empty URL if unset. */ virtual QUrl workingDirectory() const; /** * Set a working directory for the job's process. * Effective if \ref workingDirectory() hasn't been overridden. * * @param directory a valid local directory URL, or an empty URL to unset. */ void setWorkingDirectory( const QUrl& directory ); /** * Get process' command line. * * @returns The command line for the process, with first element in list being the program path. */ virtual QStringList commandLine() const; /** * Append an element to the command line argument list for this process. * If no executable is set yet, it will be set instead. * Effective if \ref commandLine() hasn't been overridden. * * @param argument the argument to add */ OutputExecuteJob& operator<<( const QString& argument ); /** * Append a list of elements to the command line argument list for this process. * If no executable is set yet, it will be set from the first argument in given list. * Effective if \ref commandLine() hasn't been overridden. * * @param arguments the arguments to add */ OutputExecuteJob& operator<<( const QStringList& arguments ); /** * Get the privilege escalation command ("su", "sudo", etc.) used for the job's process. * * @returns The privilege escalation command name and arguments; empty list if not set. */ virtual QStringList privilegedExecutionCommand() const; /** * Set the privilege escalation command ("su", "sudo", etc.) which will be used for the job's process. * Effective if \ref privilegedExecutionCommand() hasn't been overridden. * * @param command The privilege escalation command's name and arguments; empty list to unset. * @see privilegedCommand */ void setPrivilegedExecutionCommand( const QStringList& command ); /** * A convenience function to set the job name. * * Calls \ref setTitle() and \ref setObjectName(). * * @note If you need the command-line to be appended to the job name, * make sure that it is already configured upon calling this function. * * @param name The name to set; empty string to use default (process string). */ void setJobName( const QString& name ); /** * Set one of the standard filtering strategies for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** * Set the filtering strategy for the output model. */ void setFilteringStrategy(IFilterStrategy* filterStrategy); /** * Get the current properties of the job. * * @note Default-set properties are: \ref DisplayStdout. */ virtual JobProperties properties() const; /** * Set properties of the job. * Effective if \ref properties() hasn't been overridden. * * @param properties Which flags to add to the job. * @param override Whether to assign instead of doing bitwise OR. * @see JobProperties, properties(), unsetProperties() */ void setProperties( JobProperties properties, bool override = false ); /** * Unset properties of the job. * * @param properties Which flags to remove from the job * @see JobProperties, properties(), setProperties() */ void unsetProperties( JobProperties properties ); /** * Add a variable to the job's process environment. * * The variables added with this method override ones from the system environment and * the global environment profile, but are overridden by "PortableMessages" property. * * @param name The name of a variable to add * @param value The value of a variable to add; empty string to unset. */ void addEnvironmentOverride( const QString& name, const QString& value ); /** * Remove a variable from the override set. * * @param name The name of a variable to remove. * @note This does not force a variable to empty value; this is to undo the overriding itself. */ void removeEnvironmentOverride( const QString& name ); /** * Get the global environment profile name for the job's process. * * @returns The environment profile name to use in the job's process; empty if unset. */ virtual QString environmentProfile() const; /** * Set the environment profile name for the job's process. * Effective if \ref environmentProfile() hasn't been overridden. * * @param profile The name of profile to set. */ void setEnvironmentProfile( const QString& profile ); void start() override; /** * If @p executeHost is enabled, the process will be executed in the local host. * Otherwise the currentRuntime will be used to execute the process. * * @sa IRuntimeController::setCurrentRuntime() */ void setExecuteOnHost(bool executeHost); bool executeOnHost() const; protected: bool doKill() override; protected Q_SLOTS: // Redefine these functions if you want to post-process the output somehow // before it hits the output model. // Default implementations for either function call "model()->appendLines( lines );". // Do the same if you need the output to be visible. virtual void postProcessStdout( const QStringList& lines ); virtual void postProcessStderr( const QStringList& lines ); // Redefine these functions if you want to handle process' exit codes in a special manner. // One possible usage is in "cvs diff" job which returns 1 on success. virtual void childProcessExited( int exitCode, QProcess::ExitStatus exitStatus ); virtual void childProcessError( QProcess::ProcessError processError ); private: const QScopedPointer d; friend class OutputExecuteJobPrivate; }; } // namespace KDevelop -Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::OutputExecuteJob::JobProperties); +Q_DECLARE_OPERATORS_FOR_FLAGS(KDevelop::OutputExecuteJob::JobProperties) #endif // KDEVPLATFORM_OUTPUTEXECUTEJOB_H diff --git a/kdevplatform/outputview/outputmodel.cpp b/kdevplatform/outputview/outputmodel.cpp index 9d0596a328..5c06ee66e9 100644 --- a/kdevplatform/outputview/outputmodel.cpp +++ b/kdevplatform/outputview/outputmodel.cpp @@ -1,471 +1,471 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "outputmodel.h" #include "filtereditem.h" #include "outputfilteringstrategies.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QVector) namespace KDevelop { /** * Number of lines that are processed in one go before we notify the GUI thread * about the result. It is generally faster to add multiple items to a model * in one go compared to adding each item independently. */ static const int BATCH_SIZE = 50; /** * Time in ms that we wait in the parse worker for new incoming lines before * actually processing them. If we already have enough for one batch though * we process immediately. */ static const int BATCH_AGGREGATE_TIME_DELAY = 50; class ParseWorker : public QObject { Q_OBJECT public: ParseWorker() : QObject(nullptr) , m_filter(new NoFilterStrategy) , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ParseWorker::process); } public Q_SLOTS: void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy ) { m_filter = QSharedPointer( newFilterStrategy ); } void addLines( const QStringList& lines ) { m_cachedLines << lines; if (m_cachedLines.size() >= BATCH_SIZE) { // if enough lines were added, process immediately m_timer->stop(); process(); } else if (!m_timer->isActive()) { m_timer->start(); } } void flushBuffers() { m_timer->stop(); process(); emit allDone(); } Q_SIGNALS: void parsedBatch(const QVector& filteredItems); void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private Q_SLOTS: /** * Process *all* cached lines, emit parsedBatch for each batch */ void process() { QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); // apply pre-filtering functions std::transform(m_cachedLines.constBegin(), m_cachedLines.constEnd(), m_cachedLines.begin(), &KDevelop::stripAnsiSequences); // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { item = m_filter->actionInLine(line); } filteredItems << item; auto progress = m_filter->progressInLine(line); if (progress.percent >= 0 && m_progress.percent != progress.percent) { m_progress = progress; emit this->progress(m_progress); } if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); } } // Make sure to emit the rest as well if( !filteredItems.isEmpty() ) { emit parsedBatch(filteredItems); } m_cachedLines.clear(); } private: QSharedPointer m_filter; QStringList m_cachedLines; QTimer* m_timer; IFilterStrategy::Progress m_progress; }; class ParsingThread { public: ParsingThread() { m_thread.setObjectName(QStringLiteral("OutputFilterThread")); } virtual ~ParsingThread() { if (m_thread.isRunning()) { m_thread.quit(); m_thread.wait(); } } void addWorker(ParseWorker* worker) { if (!m_thread.isRunning()) { m_thread.start(); } worker->moveToThread(&m_thread); } private: QThread m_thread; }; -Q_GLOBAL_STATIC(ParsingThread, s_parsingThread); +Q_GLOBAL_STATIC(ParsingThread, s_parsingThread) class OutputModelPrivate { public: explicit OutputModelPrivate( OutputModel* model, const QUrl& builddir = QUrl() ); ~OutputModelPrivate(); bool isValidIndex( const QModelIndex&, int currentRowCount ) const; OutputModel* model; ParseWorker* worker; QVector m_filteredItems; // We use std::set because that is ordered std::set m_errorItems; // Indices of all items that we want to move to using previous and next QUrl m_buildDir; void linesParsed(const QVector& items) { model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1); foreach( const FilteredItem& item, items ) { if( item.type == FilteredItem::ErrorItem ) { m_errorItems.insert(m_filteredItems.size()); } m_filteredItems << item; } model->endInsertRows(); } }; OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const QUrl& builddir) : model(model_) , worker(new ParseWorker ) , m_buildDir( builddir ) { qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); model->connect(worker, &ParseWorker::progress, model, &OutputModel::progress); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const { return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 ); } OutputModelPrivate::~OutputModelPrivate() { worker->deleteLater(); } OutputModel::OutputModel( const QUrl& builddir, QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this, builddir ) ) { } OutputModel::OutputModel( QObject* parent ) : QAbstractListModel(parent) , d( new OutputModelPrivate( this ) ) { } OutputModel::~OutputModel() = default; QVariant OutputModel::data(const QModelIndex& idx , int role ) const { if( d->isValidIndex(idx, rowCount()) ) { switch( role ) { case Qt::DisplayRole: return d->m_filteredItems.at( idx.row() ).originalLine; break; case OutputModel::OutputItemTypeRole: return static_cast(d->m_filteredItems.at( idx.row() ).type); break; case Qt::FontRole: return QFontDatabase::systemFont(QFontDatabase::FixedFont); break; default: break; } } return QVariant(); } int OutputModel::rowCount( const QModelIndex& parent ) const { if( !parent.isValid() ) return d->m_filteredItems.count(); return 0; } QVariant OutputModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } void OutputModel::activate( const QModelIndex& index ) { if( index.model() != this || !d->isValidIndex(index, rowCount()) ) { return; } qCDebug(OUTPUTVIEW) << "Model activated" << index.row(); FilteredItem item = d->m_filteredItems.at( index.row() ); if( item.isActivatable ) { qCDebug(OUTPUTVIEW) << "activating:" << item.lineNo << item.url; KTextEditor::Cursor range( item.lineNo, item.columnNo ); KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController(); QUrl url = item.url; if (item.url.isEmpty()) { qCWarning(OUTPUTVIEW) << "trying to open empty url"; return; } if(url.isRelative()) { url = d->m_buildDir.resolved(url); } Q_ASSERT(!url.isRelative()); docCtrl->openDocument( url, range ); } else { qCDebug(OUTPUTVIEW) << "not an activateable item"; } } QModelIndex OutputModel::firstHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.begin(), 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::nextHighlightIndex( const QModelIndex ¤tIdx ) { int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0; if( !d->m_errorItems.empty() ) { qCDebug(OUTPUTVIEW) << "searching next error"; // Jump to the next error item std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow ); if( next == d->m_errorItems.end() ) next = d->m_errorItems.begin(); return index( *next, 0, QModelIndex() ); } for( int row = 0; row < rowCount(); ++row ) { int currow = (startrow + row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::previousHighlightIndex( const QModelIndex ¤tIdx ) { //We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation. int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1; if(!d->m_errorItems.empty()) { qCDebug(OUTPUTVIEW) << "searching previous error"; // Jump to the previous error item std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() ); if( previous == d->m_errorItems.begin() ) previous = d->m_errorItems.end(); --previous; return index( *previous, 0, QModelIndex() ); } for ( int row = 0; row < rowCount(); ++row ) { int currow = (startrow - row) % rowCount(); if( d->m_filteredItems.at( currow ).isActivatable ) { return index( currow, 0, QModelIndex() ); } } return QModelIndex(); } QModelIndex OutputModel::lastHighlightIndex() { if( !d->m_errorItems.empty() ) { return index( *d->m_errorItems.rbegin(), 0, QModelIndex() ); } for( int row = rowCount()-1; row >=0; --row ) { if( d->m_filteredItems.at( row ).isActivatable ) { return index( row, 0, QModelIndex() ); } } return QModelIndex(); } void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy) { // TODO: Turn into factory, decouple from OutputModel IFilterStrategy* filter = nullptr; switch( currentStrategy ) { case NoFilter: filter = new NoFilterStrategy; break; case CompilerFilter: filter = new CompilerFilterStrategy( d->m_buildDir ); break; case ScriptErrorFilter: filter = new ScriptErrorFilterStrategy; break; case NativeAppErrorFilter: filter = new NativeAppErrorFilterStrategy; break; case StaticAnalysisFilter: filter = new StaticAnalysisFilterStrategy; break; } if (!filter) { filter = new NoFilterStrategy; } QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filter)); } void OutputModel::setFilteringStrategy(IFilterStrategy* filterStrategy) { QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", Q_ARG(KDevelop::IFilterStrategy*, filterStrategy)); } void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() ) return; QMetaObject::invokeMethod(d->worker, "addLines", Q_ARG(QStringList, lines)); } void OutputModel::appendLine( const QString& line ) { appendLines( QStringList() << line ); } void OutputModel::ensureAllDone() { QMetaObject::invokeMethod(d->worker, "flushBuffers"); } void OutputModel::clear() { ensureAllDone(); beginResetModel(); d->m_filteredItems.clear(); endResetModel(); } } #include "outputmodel.moc" #include "moc_outputmodel.cpp" diff --git a/kdevplatform/project/projectmodel.cpp b/kdevplatform/project/projectmodel.cpp index 62d9011be0..793ff4a48d 100644 --- a/kdevplatform/project/projectmodel.cpp +++ b/kdevplatform/project/projectmodel.cpp @@ -1,1168 +1,1168 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmodel.h" #include #include #include #include #include #include #include #include #include "interfaces/iprojectfilemanager.h" #include #include "debug.h" #include "path.h" namespace KDevelop { QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ) { QStringList result = fullpath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) ); if( basePath.count() >= fullpath.count() ) { return QStringList(); } for( int i = 0; i < basePath.count(); i++ ) { result.takeFirst(); } } return result; } QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ) { QStringList basePath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); basePath = model->pathFromIndex( model->indexFromItem( item ) ); } return basePath + partialpath; } inline uint indexForPath( const Path& path ) { return IndexedString::indexForString(path.pathOrUrl()); } class ProjectModelPrivate { public: explicit ProjectModelPrivate( ProjectModel* model ): model( model ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { return nullptr; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: ProjectBaseItemPrivate() {} IProject* project = nullptr; ProjectBaseItem* parent = nullptr; int row = -1; QList children; QString text; ProjectBaseItem::ProjectItemType type; Qt::ItemFlags flags; ProjectModel* model = nullptr; Path m_path; uint m_pathIndex = 0; QString iconName; ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName) { if (item->parent()) { foreach(ProjectBaseItem* sibling, item->parent()->children()) { if (sibling->text() == newName) { return ProjectBaseItem::ExistingItemSameName; } } } item->setText( newName ); return ProjectBaseItem::RenameOk; } ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName) { Q_ASSERT(item->file() || item->folder()); if (newName.contains(QLatin1Char('/'))) { return ProjectBaseItem::InvalidNewName; } if (item->text() == newName) { return ProjectBaseItem::RenameOk; } Path newPath = item->path(); newPath.setLastPathSegment(newName); auto job = KIO::stat(newPath.toUrl(), KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo); if (job->exec()) { // file/folder exists already return ProjectBaseItem::ExistingItemSameName; } if( !item->project() || !item->project()->projectFileManager() ) { return renameBaseItem(item, newName); } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) { return ProjectBaseItem::RenameOk; } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) { return ProjectBaseItem::RenameOk; } else { return ProjectBaseItem::ProjectManagerRenameFailed; } } }; ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : d_ptr(new ProjectBaseItemPrivate) { Q_ASSERT(!name.isEmpty() || !parent); Q_D(ProjectBaseItem); d->project = project; d->text = name; d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if( parent ) { parent->appendRow( this ); } } ProjectBaseItem::~ProjectBaseItem() { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } if( parent() ) { parent()->takeRow( d->row ); } else if( model() ) { model()->takeRow( d->row ); } removeRows(0, d->children.size()); } ProjectBaseItem* ProjectBaseItem::child( int row ) const { Q_D(const ProjectBaseItem); if( row < 0 || row >= d->children.length() ) { return nullptr; } return d->children.at( row ); } QList< ProjectBaseItem* > ProjectBaseItem::children() const { Q_D(const ProjectBaseItem); return d->children; } ProjectBaseItem* ProjectBaseItem::takeRow(int row) { Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row < d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row); } ProjectBaseItem* olditem = d->children.takeAt( row ); olditem->d_func()->parent = nullptr; olditem->d_func()->row = -1; olditem->setModel( nullptr ); for(int i=row; id_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } if( model() ) { model()->endRemoveRows(); } return olditem; } void ProjectBaseItem::removeRow( int row ) { delete takeRow( row ); } void ProjectBaseItem::removeRows(int row, int count) { if (!count) { return; } Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row + count <= d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row + count - 1); } //NOTE: we unset parent, row and model manually to speed up the deletion if (row == 0 && count == d->children.size()) { // optimize if we want to delete all foreach(ProjectBaseItem* item, d->children) { item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); item->d_func()->parent = nullptr; item->d_func()->row = -1; item->setModel( nullptr ); delete d->children.takeAt( row ); } for(int i = row; i < d->children.size(); ++i) { d->children.at(i)->d_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } } if( model() ) { model()->endRemoveRows(); } } QModelIndex ProjectBaseItem::index() const { if( model() ) { return model()->indexFromItem( this ); } return QModelIndex(); } int ProjectBaseItem::rowCount() const { Q_D(const ProjectBaseItem); return d->children.count(); } int ProjectBaseItem::type() const { return ProjectBaseItem::BaseItem; } ProjectModel* ProjectBaseItem::model() const { Q_D(const ProjectBaseItem); return d->model; } ProjectBaseItem* ProjectBaseItem::parent() const { Q_D(const ProjectBaseItem); if( model() && model()->d->rootItem == d->parent ) { return nullptr; } return d->parent; } int ProjectBaseItem::row() const { Q_D(const ProjectBaseItem); return d->row; } QString ProjectBaseItem::text() const { Q_D(const ProjectBaseItem); if( project() && !parent() ) { return project()->name(); } else { return d->text; } } void ProjectBaseItem::setModel( ProjectModel* model ) { Q_D(ProjectBaseItem); if (model == d->model) { return; } if (d->model && d->m_pathIndex) { d->model->d->pathLookupTable.remove(d->m_pathIndex, this); } d->model = model; if (model && d->m_pathIndex) { model->d->pathLookupTable.insert(d->m_pathIndex, this); } foreach( ProjectBaseItem* item, d->children ) { item->setModel( model ); } } void ProjectBaseItem::setRow( int row ) { Q_D(ProjectBaseItem); d->row = row; } void ProjectBaseItem::setText( const QString& text ) { Q_ASSERT(!text.isEmpty() || !parent()); Q_D(ProjectBaseItem); d->text = text; if( d->model ) { QModelIndex idx = index(); emit d->model->dataChanged(idx, idx); } } ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName) { Q_D(ProjectBaseItem); return d->renameBaseItem(this, newName); } KDevelop::ProjectBaseItem::ProjectItemType baseType( int type ) { if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder ) return KDevelop::ProjectBaseItem::Folder; if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget || type == KDevelop::ProjectBaseItem::LibraryTarget) return KDevelop::ProjectBaseItem::Target; return static_cast( type ); } bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const { if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) { // For custom types we want to make sure that if they override lessThan, then we // prefer their lessThan implementation return !item->lessThan( this ); } KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type()); if(leftType==rightType) { if(leftType==KDevelop::ProjectBaseItem::File) { return file()->fileName().compare(item->file()->fileName(), Qt::CaseInsensitive) < 0; } return this->text()text(); } else { return leftTypepath() < item2->path(); } IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); return d->project; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) { Q_D(ProjectBaseItem); if( !item ) { return; } if( item->parent() ) { // Proper way is to first removeRow() on the original parent, then appendRow on this one qCWarning(PROJECT) << "Ignoring double insertion of item" << item; return; } // this is too slow... O(n) and thankfully not a problem anyways // Q_ASSERT(!d->children.contains(item)); int startrow,endrow; if( model() ) { startrow = endrow = d->children.count(); model()->beginInsertRows(index(), startrow, endrow); } d->children.append( item ); item->setRow( d->children.count() - 1 ); item->d_func()->parent = this; item->setModel( model() ); if( model() ) { model()->endInsertRows(); } } Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); return d->m_path; } QString ProjectBaseItem::baseName() const { return text(); } void ProjectBaseItem::setPath( const Path& path) { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } d->m_path = path; d->m_pathIndex = indexForPath(path); setText( path.lastPathSegment() ); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.insert(d->m_pathIndex, this); } } Qt::ItemFlags ProjectBaseItem::flags() { Q_D(ProjectBaseItem); return d->flags; } Qt::DropActions ProjectModel::supportedDropActions() const { return (Qt::DropActions)(Qt::MoveAction); } void ProjectBaseItem::setFlags(Qt::ItemFlags flags) { Q_D(ProjectBaseItem); d->flags = flags; if(d->model) emit d->model->dataChanged(index(), index()); } QString ProjectBaseItem::iconName() const { return QString(); } ProjectFolderItem *ProjectBaseItem::folder() const { return nullptr; } ProjectTargetItem *ProjectBaseItem::target() const { return nullptr; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { return nullptr; } ProjectFileItem *ProjectBaseItem::file() const { return nullptr; } QList ProjectBaseItem::folderList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Folder || item->type() == BuildFolder ) { ProjectFolderItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::targetList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget ) { ProjectTargetItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); Q_ASSERT(item); if ( item && item->type() == File ) { ProjectFileItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } void ProjectModel::clear() { d->rootItem->removeRows(0, d->rootItem->rowCount()); } ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setPath( path ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project && project->path() != path) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent ) : ProjectBaseItem( parent->project(), name, parent ) { setPath( Path(parent->path(), name) ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project() && project()->path() != path()) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::~ProjectFolderItem() { } void ProjectFolderItem::setPath( const Path& path ) { ProjectBaseItem::setPath(path); propagateRename(path); } ProjectFolderItem *ProjectFolderItem::folder() const { return const_cast(this); } int ProjectFolderItem::type() const { return ProjectBaseItem::Folder; } QString ProjectFolderItem::folderName() const { return baseName(); } void ProjectFolderItem::propagateRename( const Path& newBase ) const { Path path = newBase; path.addPath(QStringLiteral("dummy")); foreach( KDevelop::ProjectBaseItem* child, children() ) { path.setLastPathSegment( child->text() ); child->setPath( path ); const ProjectFolderItem* folder = child->folder(); if ( folder ) { folder->propagateRename( path ); } } } ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } bool ProjectFolderItem::hasFileOrFolder(const QString& name) const { foreach ( ProjectBaseItem* item, children() ) { if ( (item->type() == Folder || item->type() == File || item->type() == BuildFolder) && name == item->baseName() ) { return true; } } return false; } bool ProjectBaseItem::isProjectRoot() const { return parent()==nullptr; } ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent) : ProjectFolderItem( project, path, parent ) { } ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent ) : ProjectFolderItem( name, parent ) { } QString ProjectFolderItem::iconName() const { return QStringLiteral("folder"); } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { return QStringLiteral("folder-development"); } ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( path ); } ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent ) : ProjectBaseItem( parent->project(), name, parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( Path(parent->path(), name) ); } ProjectFileItem::~ProjectFileItem() { if( project() && d_ptr->m_pathIndex ) { project()->removeFromFileSet( this ); } } IndexedString ProjectFileItem::indexedPath() const { return IndexedString::fromIndex( d_ptr->m_pathIndex ); } ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } QString ProjectFileItem::fileName() const { return baseName(); } // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap static const int maximumCacheExtensionLength = 3; bool isNumeric(const QStringRef& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str.at(a).isNumber()) return false; return true; } class IconNameCache { public: QString iconNameForPath(const Path& path, const QString& fileName) { // find icon name based on file extension, if possible QString extension; int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) { QStringRef extRef = fileName.midRef(extensionStart + 1); if( isNumeric(extRef) ) { // don't cache numeric extensions extRef.clear(); } if( !extRef.isEmpty() ) { extension = extRef.toString(); QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension ); if( it != fileExtensionToIcon.constEnd() ) { return *it; } } } QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name()); QString iconName; if ( it == mimeToIcon.constEnd() ) { iconName = mime.iconName(); if (iconName.isEmpty()) { iconName = QStringLiteral("none"); } mimeToIcon.insert(mime.name(), iconName); } else { iconName = *it; } if ( !extension.isEmpty() ) { fileExtensionToIcon.insert(extension, iconName); } return iconName; } QMutex mutex; QHash mimeToIcon; QHash fileExtensionToIcon; }; -Q_GLOBAL_STATIC(IconNameCache, s_cache); +Q_GLOBAL_STATIC(IconNameCache, s_cache) QString ProjectFileItem::iconName() const { // think of d_ptr->iconName as mutable, possible since d_ptr is not const if (d_ptr->iconName.isEmpty()) { // lazy load implementation of icon lookup d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text ); // we should always get *some* icon name back Q_ASSERT(!d_ptr->iconName.isEmpty()); } return d_ptr->iconName; } void ProjectFileItem::setPath( const Path& path ) { if (path == d_ptr->m_path) { return; } if( project() && d_ptr->m_pathIndex ) { // remove from fileset if we are in there project()->removeFromFileSet( this ); } ProjectBaseItem::setPath( path ); if( project() && d_ptr->m_pathIndex ) { // add to fileset with new path project()->addToFileSet( this ); } // invalidate icon name for future lazy-loaded updated d_ptr->iconName.clear(); } int ProjectFileItem::type() const { return ProjectBaseItem::File; } ProjectFileItem *ProjectFileItem::file() const { return const_cast( this ); } ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectBaseItem( project, name, parent ) { setFlags(flags() | Qt::ItemIsDropEnabled); } QString ProjectTargetItem::iconName() const { return QStringLiteral("system-run"); } void ProjectTargetItem::setPath( const Path& path ) { // don't call base class, it calls setText with the new path's filename // which we do not want for target items d_ptr->m_path = path; } int ProjectTargetItem::type() const { return ProjectBaseItem::Target; } ProjectTargetItem *ProjectTargetItem::target() const { return const_cast( this ); } ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) { } ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const { return const_cast( this ); } int ProjectExecutableTargetItem::type() const { return ProjectBaseItem::ExecutableTarget; } ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) {} int ProjectLibraryTargetItem::type() const { return ProjectBaseItem::LibraryTarget; } QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const { if(tofetch_.isEmpty()) return QModelIndex(); QStringList tofetch(tofetch_); if(tofetch.last().isEmpty()) tofetch.takeLast(); QModelIndex current=index(0,0, QModelIndex()); QModelIndex ret; for(int a = 0; a < tofetch.size(); ++a) { const QString& currentName = tofetch[a]; bool matched = false; QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly); foreach(const QModelIndex& idx, l) { //If this is not the last item, only match folders, as there may be targets and folders with the same name if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) { ret = idx; current = index(0,0, ret); matched = true; break; } } if(!matched) { ret = QModelIndex(); break; } } Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last()); return ret; } QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QStringList(); QModelIndex idx = index; QStringList list; do { QString t = data(idx, Qt::DisplayRole).toString(); list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); return list; } int ProjectModel::columnCount( const QModelIndex& ) const { return 1; } int ProjectModel::rowCount( const QModelIndex& parent ) const { ProjectBaseItem* item = d->itemFromIndex( parent ); return item ? item->rowCount() : 0; } QModelIndex ProjectModel::parent( const QModelIndex& child ) const { if( child.isValid() ) { ProjectBaseItem* item = static_cast( child.internalPointer() ); return indexFromItem( item ); } return QModelIndex(); } QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const { if( item && item->d_func()->parent ) { return createIndex( item->row(), 0, item->d_func()->parent ); } return QModelIndex(); } ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const { if( index.row() >= 0 && index.column() == 0 && index.model() == this ) { ProjectBaseItem* parent = static_cast( index.internalPointer() ); if( parent ) { return parent->child( index.row() ); } } return nullptr; } QVariant ProjectModel::data( const QModelIndex& index, int role ) const { static const QSet allowedRoles = { Qt::DisplayRole, Qt::ToolTipRole, Qt::DecorationRole, ProjectItemRole, ProjectRole, UrlRole }; if( allowedRoles.contains(role) && index.isValid() ) { ProjectBaseItem* item = itemFromIndex( index ); if( item ) { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(item->iconName()); case Qt::ToolTipRole: return item->path().pathOrUrl(); case Qt::DisplayRole: return item->text(); case ProjectItemRole: return QVariant::fromValue(item); case UrlRole: return item->path().toUrl(); case ProjectRole: return QVariant::fromValue(item->project()); } } } return QVariant(); } ProjectModel::ProjectModel( QObject *parent ) : QAbstractItemModel( parent ), d( new ProjectModelPrivate( this ) ) { d->rootItem = new ProjectBaseItem( nullptr, QString(), nullptr ); d->rootItem->setModel( this ); } ProjectModel::~ProjectModel() { d->rootItem->setModel(nullptr); delete d->rootItem; } ProjectVisitor::ProjectVisitor() { } QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const { ProjectBaseItem* parentItem = d->itemFromIndex( parent ); if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) { return createIndex( row, column, parentItem ); } return QModelIndex(); } void ProjectModel::appendRow( ProjectBaseItem* item ) { d->rootItem->appendRow( item ); } void ProjectModel::removeRow( int row ) { d->rootItem->removeRow( row ); } ProjectBaseItem* ProjectModel::takeRow( int row ) { return d->rootItem->takeRow( row ); } ProjectBaseItem* ProjectModel::itemAt(int row) const { return d->rootItem->child(row); } QList< ProjectBaseItem* > ProjectModel::topItems() const { return d->rootItem->children(); } Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const { ProjectBaseItem* item = itemFromIndex( index ); if(item) return item->flags(); return Qt::NoItemFlags; } bool ProjectModel::insertColumns(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::insertRows(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::setData(const QModelIndex&, const QVariant&, int) { // Not supported return false; } QList ProjectModel::itemsForPath(const IndexedString& path) const { return d->pathLookupTable.values(path.index()); } ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const { return d->pathLookupTable.value(path.index()); } void ProjectVisitor::visit( ProjectModel* model ) { foreach( ProjectBaseItem* item, model->topItems() ) { visit( item->project() ); } } void ProjectVisitor::visit ( IProject* prj ) { visit( prj->projectItem() ); } void ProjectVisitor::visit ( ProjectBuildFolderItem* folder ) { visit(static_cast(folder)); } void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec ) { foreach( ProjectFileItem* item, exec->fileList() ) { visit( item ); } } void ProjectVisitor::visit ( ProjectFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectFileItem* ) { } void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib ) { foreach( ProjectFileItem* item, lib->fileList() ) { visit( item ); } } ProjectVisitor::~ProjectVisitor() { } } diff --git a/kdevplatform/serialization/tests/bench_itemrepository.cpp b/kdevplatform/serialization/tests/bench_itemrepository.cpp index 359b3b8049..86c404341c 100644 --- a/kdevplatform/serialization/tests/bench_itemrepository.cpp +++ b/kdevplatform/serialization/tests/bench_itemrepository.cpp @@ -1,218 +1,218 @@ /* * This file is part of KDevelop * Copyright 2012-2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "bench_itemrepository.h" #include #include #include #include -QTEST_GUILESS_MAIN(BenchItemRepository); +QTEST_GUILESS_MAIN(BenchItemRepository) using namespace KDevelop; struct TestData { uint length; uint itemSize() const { return sizeof(TestData) + length; } uint hash() const { const char* str = ((const char*)this) + sizeof(TestData); return IndexedString::hashString(str, length); } }; struct TestDataRepositoryItemRequest { //The text is supposed to be utf8 encoded TestDataRepositoryItemRequest(const char* text, uint length) : m_length(length) , m_text(text) , m_hash(IndexedString::hashString(text, length)) { } enum { AverageSize = 10 //This should be the approximate average size of an Item }; typedef uint HashType; //Should return the hash-value associated with this request(For example the hash of a string) HashType hash() const { return m_hash; } //Should return the size of an item created with createItem uint itemSize() const { return sizeof(TestData) + m_length; } //Should create an item where the information of the requested item is permanently stored. The pointer //@param item equals an allocated range with the size of itemSize(). void createItem(TestData* item) const { item->length = m_length; ++item; memcpy(item, m_text, m_length); } static void destroy(TestData* item, AbstractItemRepository&) { Q_UNUSED(item); //Nothing to do here (The object is not intelligent) } static bool persistent(const TestData* item) { Q_UNUSED(item); return true; } //Should return whether the here requested item equals the given item bool equals(const TestData* item) const { return item->length == m_length && (memcmp(++item, m_text, m_length) == 0); } unsigned short m_length; const char* m_text; unsigned int m_hash; }; typedef ItemRepository TestDataRepository; void BenchItemRepository::initTestCase() { ItemRepositoryRegistry::initialize(m_repositoryPath); } void BenchItemRepository::cleanupTestCase() { ItemRepositoryRegistry::deleteRepositoryFromDisk(m_repositoryPath); } static QVector generateData() { QVector data; static const int NUM_ITEMS = 100000; data.resize(NUM_ITEMS); for(int i = 0; i < NUM_ITEMS; ++i) { data[i] = QStringLiteral("/foo/%1").arg(i); } return data; } static QVector insertData(const QVector& data, TestDataRepository& repo) { QVector indices; indices.reserve(data.size()); foreach(const QString& item, data) { const QByteArray byteArray = item.toUtf8(); indices << repo.index(TestDataRepositoryItemRequest(byteArray.constData(), byteArray.length())); } return indices; } void BenchItemRepository::insert() { TestDataRepository repo("TestDataRepositoryInsert"); QVector data = generateData(); QVector indices; QBENCHMARK_ONCE { indices = insertData(data, repo); repo.store(); } Q_ASSERT(indices.size() == data.size()); QCOMPARE(repo.statistics().totalItems, uint(data.size())); } void BenchItemRepository::remove() { TestDataRepository repo("TestDataRepositoryRemove"); QVector data = generateData(); QVector indices = insertData(data, repo); repo.store(); QVERIFY(indices.size() == indices.toList().toSet().size()); QVERIFY(indices.size() == data.size()); QBENCHMARK_ONCE { foreach(uint index, indices) { repo.deleteItem(index); } repo.store(); } QCOMPARE(repo.statistics().totalItems, 0u); } void BenchItemRepository::removeDisk() { QVector data = generateData(); QVector indices; { TestDataRepository repo("TestDataRepositoryRemoveDisk"); indices = insertData(data, repo); repo.store(); } TestDataRepository repo("TestDataRepositoryRemoveDisk"); QVERIFY(repo.statistics().totalItems == static_cast(data.size())); QBENCHMARK_ONCE { foreach(uint index, indices) { repo.deleteItem(index); } repo.store(); } QCOMPARE(repo.statistics().totalItems, 0u); } void BenchItemRepository::lookupKey() { TestDataRepository repo("TestDataRepositoryLookupKey"); QVector data = generateData(); QVector indices = insertData(data, repo); srand(0); std::random_shuffle(indices.begin(), indices.end()); QBENCHMARK { foreach(uint index, indices) { repo.itemFromIndex(index); } } } void BenchItemRepository::lookupValue() { TestDataRepository repo("TestDataRepositoryLookupValue"); QVector data = generateData(); QVector indices = insertData(data, repo); srand(0); std::random_shuffle(indices.begin(), indices.end()); QBENCHMARK { foreach(const QString& item, data) { const QByteArray byteArray = item.toUtf8(); repo.findIndex(TestDataRepositoryItemRequest(byteArray.constData(), byteArray.length())); } } } diff --git a/kdevplatform/serialization/tests/test_indexedstring.cpp b/kdevplatform/serialization/tests/test_indexedstring.cpp index 73c27d4f5a..cefaaaa936 100644 --- a/kdevplatform/serialization/tests/test_indexedstring.cpp +++ b/kdevplatform/serialization/tests/test_indexedstring.cpp @@ -1,248 +1,248 @@ /* * This file is part of KDevelop * Copyright 2012-2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_indexedstring.h" #include #include #include #include #include -QTEST_GUILESS_MAIN(TestIndexedString); +QTEST_GUILESS_MAIN(TestIndexedString) using namespace KDevelop; void TestIndexedString::initTestCase() { ItemRepositoryRegistry::initialize(m_repositoryPath); } void TestIndexedString::cleanupTestCase() { ItemRepositoryRegistry::deleteRepositoryFromDisk(m_repositoryPath); } void TestIndexedString::testUrl_data() { QTest::addColumn("url"); QTest::addColumn("string"); QTest::newRow("empty") << QUrl() << QString(); QTest::newRow("/") << QUrl::fromLocalFile(QStringLiteral("/")) << QStringLiteral("/"); QTest::newRow("/foo/bar") << QUrl::fromLocalFile(QStringLiteral("/foo/bar")) << QStringLiteral("/foo/bar"); QTest::newRow("http://foo.com/") << QUrl(QStringLiteral("http://foo.com/")) << QStringLiteral("http://foo.com/"); QTest::newRow("http://foo.com/bar/asdf") << QUrl(QStringLiteral("http://foo.com/bar/asdf")) << QStringLiteral("http://foo.com/bar/asdf"); QTest::newRow("file:///bar/asdf") << QUrl(QStringLiteral("file:///bar/asdf")) << QStringLiteral("/bar/asdf"); #ifdef Q_OS_WIN // Make sure we're not running into https://bugreports.qt.io/browse/QTBUG-41729 QTest::newRow("file:///C:/bar/asdf") << QUrl("file:///C:/bar/asdf") << QStringLiteral("C:/bar/asdf"); #endif } void TestIndexedString::testUrl() { QFETCH(QUrl, url); IndexedString indexed(url); QCOMPARE(indexed.toUrl(), url); QTEST(indexed.str(), "string"); } static QVector generateData() { QVector data; static const int NUM_ITEMS = 100000; data.resize(NUM_ITEMS); for(int i = 0; i < NUM_ITEMS; ++i) { data[i] = QStringLiteral("/foo/%1").arg(i); } return data; } void TestIndexedString::bench_index() { QVector data = generateData(); QBENCHMARK { foreach(const QString& item, data) { IndexedString idx(item); Q_UNUSED(idx); } } } static QVector setupTest() { QVector data = generateData(); QVector indices; indices.reserve(data.size()); foreach(const QString& item, data) { IndexedString idx(item); indices << idx.index(); } return indices; } void TestIndexedString::bench_length() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.length(); } } } void TestIndexedString::bench_qstring() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.str(); } } } void TestIndexedString::bench_kurl() { QVector indices = setupTest(); QBENCHMARK { foreach(uint index, indices) { IndexedString str = IndexedString::fromIndex(index); str.toUrl(); } } } void TestIndexedString::bench_qhashQString() { QVector data = generateData(); quint64 sum = 0; QBENCHMARK { foreach (const auto& string, data) { sum += qHash(string); } } QVERIFY(sum > 0); } void TestIndexedString::bench_qhashIndexedString() { QVector indices = setupTest(); quint64 sum = 0; QBENCHMARK { foreach(uint index, indices) { sum += qHash(IndexedString::fromIndex(index)); } } QVERIFY(sum > 0); } void TestIndexedString::bench_hashString() { QVector strings = generateData(); QVector byteArrays; byteArrays.reserve(strings.size()); foreach (const auto& string, strings) { byteArrays << string.toUtf8(); } quint64 sum = 0; QBENCHMARK { foreach (const auto& array, byteArrays) { sum += IndexedString::hashString(array.constData(), array.length()); } } QVERIFY(sum > 0); } void TestIndexedString::bench_kdevhash() { QVector strings = generateData(); QVector byteArrays; byteArrays.reserve(strings.size()); foreach (const auto& string, strings) { byteArrays << string.toUtf8(); } quint64 sum = 0; QBENCHMARK { foreach (const auto& array, byteArrays) { sum += KDevHash() << array; } } QVERIFY(sum > 0); } void TestIndexedString::bench_qSet() { QVector indices = setupTest(); QSet set; QBENCHMARK { foreach(uint index, indices) { set.insert(IndexedString::fromIndex(index)); } } } void TestIndexedString::test() { QFETCH(QString, data); IndexedString indexed(data); QCOMPARE(indexed.str(), data); QCOMPARE(indexed.index(), IndexedString::indexForString(data)); const auto byteArrayData = data.toUtf8(); QEXPECT_FAIL("char-utf8", "UTF-8 gets decoded and the char data is stored internally", Continue); QEXPECT_FAIL("string-utf8", "UTF-8 gets decoded and the char data is stored internally", Continue); QCOMPARE(indexed.length(), data.length()); // fallback until we rely on internal utf8 encoding QCOMPARE(indexed.length(), byteArrayData.length()); QCOMPARE(indexed.byteArray(), byteArrayData); QVERIFY(!strncmp(indexed.c_str(), byteArrayData.data(), byteArrayData.length())); QCOMPARE(indexed.index(), IndexedString::indexForString(byteArrayData.data(), byteArrayData.length())); IndexedString moved = std::move(indexed); QCOMPARE(indexed, IndexedString()); QVERIFY(indexed.isEmpty()); QCOMPARE(moved.str(), data); } void TestIndexedString::test_data() { QTest::addColumn("data"); QTest::newRow("empty") << QString(); QTest::newRow("char-ascii") << QStringLiteral("a"); QTest::newRow("char-utf8") << QStringLiteral("ä"); QTest::newRow("string-ascii") << QStringLiteral("asdf()?="); QTest::newRow("string-utf8") << QStringLiteral("æſðđäöü"); } void TestIndexedString::testCString() { IndexedString str(nullptr); QCOMPARE(str.index(), 0u); QVERIFY(str.isEmpty()); } diff --git a/kdevplatform/shell/editorconfigpage.cpp b/kdevplatform/shell/editorconfigpage.cpp index 5402341a75..9d614f6327 100644 --- a/kdevplatform/shell/editorconfigpage.cpp +++ b/kdevplatform/shell/editorconfigpage.cpp @@ -1,128 +1,128 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "editorconfigpage.h" #include #include #include #include using namespace KDevelop; namespace { class KTextEditorConfigPageAdapter : public ConfigPage { Q_OBJECT public: explicit KTextEditorConfigPageAdapter(KTextEditor::ConfigPage* page, QWidget* parent = nullptr) : ConfigPage(nullptr, nullptr, parent), m_page(page) { page->setParent(this); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(page); setLayout(layout); connect(page, &KTextEditor::ConfigPage::changed, this, &ConfigPage::changed); } ~KTextEditorConfigPageAdapter() override {} QString name() const override { return m_page->name(); } QIcon icon() const override { return m_page->icon(); } QString fullName() const override { return m_page->fullName(); } public Q_SLOTS: void apply() override { m_page->apply(); } void defaults() override { m_page->defaults(); } void reset() override { m_page->reset(); } private: KTextEditor::ConfigPage* m_page; }; } EditorConfigPage::EditorConfigPage(QWidget* parent) : ConfigPage(nullptr, nullptr, parent) { setObjectName(QStringLiteral("editorconfig")); } -EditorConfigPage::~EditorConfigPage() {}; +EditorConfigPage::~EditorConfigPage() {} QString EditorConfigPage::name() const { return i18n("Editor"); } QIcon EditorConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } QString EditorConfigPage::fullName() const { return i18n("Configure Text Editor"); } int EditorConfigPage::childPages() const { return KTextEditor::Editor::instance()->configPages(); } ConfigPage* EditorConfigPage::childPage(int number) { auto page = KTextEditor::Editor::instance()->configPage(number, this); if (page) { return new KTextEditorConfigPageAdapter(page, this); } return nullptr; } #include "editorconfigpage.moc" diff --git a/kdevplatform/shell/loadedpluginsdialog.cpp b/kdevplatform/shell/loadedpluginsdialog.cpp index 3b5916c8aa..768c857799 100644 --- a/kdevplatform/shell/loadedpluginsdialog.cpp +++ b/kdevplatform/shell/loadedpluginsdialog.cpp @@ -1,307 +1,307 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "loadedpluginsdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); -}; +} QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { Q_OBJECT public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; explicit PluginsModel(QObject* parent = nullptr) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { if (!index.isValid()) return nullptr; if (index.parent().isValid()) return nullptr; if (index.column() != 0) return nullptr; if (index.row() >= m_plugins.count()) return nullptr; return m_plugins[index.row()]; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: return pluginInfo(plugin).iconName(); default: return QVariant(); }; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: explicit LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); // only for getting size matters } ~LoadedPluginsDelegate() override { delete pushButton; } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { int i = 5; int j = 1; QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize(qMax(fmTitle.width(index.model()->data(index, Qt::DisplayRole).toString()), option.fontMetrics.width(index.model()->data(index, PluginsModel::DescriptionRole).toString())) + KIconLoader::SizeMedium + MARGIN * i + pushButton->sizeHint().width() * j, qMax(KIconLoader::SizeMedium + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets(const QModelIndex &index) const override { Q_UNUSED(index); QPushButton *button = new QPushButton(); button->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); setBlockedEventTypes(button, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if (widgets.isEmpty()) { return; } QPushButton *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { PluginsModel *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { KAboutData aboutData = KAboutData::fromPluginMetaData(pluginInfo(p)); if (!aboutData.componentName().isEmpty()) { // Be sure the about data is not completely empty KDevelop::ScopedDialog aboutPlugin(aboutData, itemView()); aboutPlugin->exec(); return; } } } private: QPushButton *pushButton; }; class PluginsView : public QListView { Q_OBJECT public: explicit PluginsView(QWidget* parent = nullptr) :QListView(parent) { setModel(new PluginsModel(this)); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } ~PluginsView() override { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { setWindowTitle(i18n("Loaded Plugins")); QVBoxLayout* vbox = new QVBoxLayout(this); KTitleWidget* title = new KTitleWidget(this); title->setPixmap(QIcon::fromTheme(KAboutData::applicationData().programIconName()), KTitleWidget::ImageLeft); title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/kdevplatform/shell/sessioncontroller.cpp b/kdevplatform/shell/sessioncontroller.cpp index 9a90324e5b..5aae860623 100644 --- a/kdevplatform/shell/sessioncontroller.cpp +++ b/kdevplatform/shell/sessioncontroller.cpp @@ -1,652 +1,652 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = nullptr; -}; +} void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << QLatin1Char('-') + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: explicit SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(nullptr) , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return nullptr; } Session* findSessionForId(const QString& idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return nullptr; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = nullptr; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = nullptr; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction(QLatin1String("session_") + s->id().toString(), a); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + qApp->applicationName() + QLatin1String("/sessions/"); } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private Q_SLOTS: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), i18n("Session Manager")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction(QStringLiteral("new_session")); connect(action, &QAction::triggered, this, [&] { d->newSession(); }); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction(QStringLiteral("rename_session")); connect(action, &QAction::triggered, this, [&] { d->renameSession(); }); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction(QStringLiteral("delete_session")); connect(action, &QAction::triggered, this, [&] { d->deleteCurrentSession(); }); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() = default; void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = nullptr; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith(QLatin1Char('{'))) { s = new Session( QUuid(name).toString(), this ); }else{ qsrand(QDateTime::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString(), this ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk(DUChain::repositoryPathForSession(lock)); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying do { Session* s = this->session(load); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id, bool doLocking) { return SessionLock::tryLockSession(id, doLocking); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(const QString& headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) // krazy:exclude=crashy { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTimeUtc().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/kdevplatform/shell/tests/test_documentcontroller.cpp b/kdevplatform/shell/tests/test_documentcontroller.cpp index 54603f7939..f2060f14e9 100644 --- a/kdevplatform/shell/tests/test_documentcontroller.cpp +++ b/kdevplatform/shell/tests/test_documentcontroller.cpp @@ -1,197 +1,197 @@ /* Unit tests for DocumentController.* Copyright 2011 Damien Flament 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 "test_documentcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; void TestDocumentController::initTestCase() { AutoTestShell::init({{}}); // do not load plugins at all TestCore::initialize(); Core::self()->languageController()->backgroundParser()->disableProcessing(); m_subject = Core::self()->documentController(); } void TestDocumentController::init() { Core::self()->documentControllerInternal()->initialize(); // create temp files m_file1.setFileTemplate(m_tempDir.path() + "/tmp_XXXXXX.txt"); m_file2.setFileTemplate(m_tempDir.path() + "/tmp_XXXXXX.txt"); if(!m_file1.open() || !m_file2.open()) { QFAIL("Can't create temp files"); } // pre-conditions QVERIFY(m_subject->openDocuments().empty()); QVERIFY(m_subject->documentForUrl(QUrl()) == nullptr); QVERIFY(m_subject->activeDocument() == nullptr); } void TestDocumentController::cleanup() { // ensure there are not opened documents for next test foreach(IDocument* document, m_subject->openDocuments()) { document->close(IDocument::Discard); } Core::self()->documentControllerInternal()->cleanup(); } void TestDocumentController::cleanupTestCase() { TestCore::shutdown(); m_tempDir.remove(); } void TestDocumentController::testOpeningNewDocumentFromText() { qRegisterMetaType("KDevelop::IDocument*"); QSignalSpy createdSpy(m_subject, SIGNAL(textDocumentCreated(KDevelop::IDocument*))); QVERIFY(createdSpy.isValid()); QSignalSpy openedSpy(m_subject, SIGNAL(documentOpened(KDevelop::IDocument*))); QVERIFY(openedSpy.isValid()); IDocument* document = m_subject->openDocumentFromText(QLatin1String("")); QVERIFY(document != nullptr); QCOMPARE(createdSpy.count(), 1); QCOMPARE(openedSpy.count(), 1); QVERIFY(!m_subject->openDocuments().empty()); QVERIFY(m_subject->documentForUrl(document->url()) == document); QVERIFY(m_subject->activeDocument() == document); } void TestDocumentController::testOpeningDocumentFromUrl() { QUrl url = QUrl::fromLocalFile(m_file1.fileName()); IDocument* document = m_subject->openDocument(url); QVERIFY(document != nullptr); } void TestDocumentController::testSaveSomeDocuments() { // create documents QTemporaryDir dir; IDocument *document1 = m_subject->openDocument(createFile(dir, QStringLiteral("foo"))); IDocument *document2 = m_subject->openDocument(createFile(dir, QStringLiteral("bar"))); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); // edit both documents document1->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); document2->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); QCOMPARE(document1->state(), IDocument::Modified); QCOMPARE(document2->state(), IDocument::Modified); // save one document (Silent == don't ask user) m_subject->saveSomeDocuments(QList() << document1, IDocument::Silent); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Modified); } void TestDocumentController::testSaveAllDocuments() { // create documents QTemporaryDir dir; IDocument *document1 = m_subject->openDocument(createFile(dir, QStringLiteral("foo"))); IDocument *document2 = m_subject->openDocument(createFile(dir, QStringLiteral("bar"))); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); // edit documents document1->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); document2->textDocument()->insertText(KTextEditor::Cursor(), QStringLiteral("some text")); QCOMPARE(document1->state(), IDocument::Modified); QCOMPARE(document2->state(), IDocument::Modified); // save documents m_subject->saveAllDocuments(IDocument::Silent); QCOMPARE(document1->state(), IDocument::Clean); QCOMPARE(document2->state(), IDocument::Clean); } void TestDocumentController::testCloseAllDocuments() { // create documents m_subject->openDocumentFromText(QLatin1String("")); m_subject->openDocumentFromText(QLatin1String("")); QVERIFY(!m_subject->openDocuments().empty()); m_subject->closeAllDocuments(); QVERIFY(m_subject->openDocuments().empty()); } QUrl TestDocumentController::createFile(const QTemporaryDir& dir, const QString& filename) { QFile file(dir.path() + filename); bool success = file.open(QIODevice::WriteOnly | QIODevice::Text); if(!success) { QWARN(QString("Failed to create file: " + dir.path() + filename).toLatin1().data()); return QUrl(); } file.close(); return QUrl::fromLocalFile(dir.path() + filename); } void TestDocumentController::testEmptyUrl() { const auto first = DocumentController::nextEmptyDocumentUrl(); QVERIFY(DocumentController::isEmptyDocumentUrl(first)); QCOMPARE(DocumentController::nextEmptyDocumentUrl(), first); auto doc = m_subject->openDocumentFromText(QString()); QCOMPARE(doc->url(), first); const auto second = DocumentController::nextEmptyDocumentUrl(); QVERIFY(first != second); QVERIFY(DocumentController::isEmptyDocumentUrl(second)); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl())); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl(QStringLiteral("http://foo.org")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl(QStringLiteral("http://foo.org/test")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl::fromLocalFile(QStringLiteral("/")))); QVERIFY(!DocumentController::isEmptyDocumentUrl(QUrl::fromLocalFile(QStringLiteral("/test")))); } -QTEST_MAIN(TestDocumentController); +QTEST_MAIN(TestDocumentController) diff --git a/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp b/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp index 10d1685254..4b9dbcbd24 100644 --- a/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp +++ b/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp @@ -1,198 +1,198 @@ /* Copyright 2015 Milian Wolff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "test_ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { template QPointer makeQPointer(T *ptr) { return {ptr}; } IToolViewFactory *findToolView(const QString &id) { const auto uiController = Core::self()->uiControllerInternal(); const auto map = uiController->factoryDocuments(); for (auto it = map.begin(); it != map.end(); ++it) { if (it.key()->id() == id) { return it.key(); } } return nullptr; } class TestPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit TestPlugin(QObject *parent) : Plugin(parent) { } QObject *createView(KTextEditor::MainWindow * mainWindow) override { return new QObject(mainWindow); } }; } void TestKTextEditorPluginIntegration::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); AutoTestShell::init({QStringLiteral("katesnippetsplugin")}); TestCore::initialize(); QVERIFY(KTextEditor::Editor::instance()); } void TestKTextEditorPluginIntegration::cleanupTestCase() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); const auto editor = makeQPointer(KTextEditor::Editor::instance()); const auto application = makeQPointer(editor->application()); const auto window = makeQPointer(application->activeMainWindow()); TestCore::shutdown(); QVERIFY(!plugin); QVERIFY(!window); QVERIFY(!application); } void TestKTextEditorPluginIntegration::testApplication() { auto app = KTextEditor::Editor::instance()->application(); QVERIFY(app); QVERIFY(app->parent()); QCOMPARE(app->parent()->metaObject()->className(), "KTextEditorIntegration::Application"); QVERIFY(app->activeMainWindow()); QCOMPARE(app->mainWindows().size(), 1); QVERIFY(app->mainWindows().contains(app->activeMainWindow())); } void TestKTextEditorPluginIntegration::testMainWindow() { auto window = KTextEditor::Editor::instance()->application()->activeMainWindow(); QVERIFY(window); QVERIFY(window->parent()); QCOMPARE(window->parent()->metaObject()->className(), "KTextEditorIntegration::MainWindow"); const auto id = QStringLiteral("kte_integration_toolview"); const auto icon = QIcon::fromTheme(QStringLiteral("kdevelop")); const auto text = QStringLiteral("some text"); QVERIFY(!findToolView(id)); auto plugin = new TestPlugin(this); auto toolView = makeQPointer(window->createToolView(plugin, id, KTextEditor::MainWindow::Bottom, icon, text)); QVERIFY(toolView); auto factory = findToolView(id); QVERIFY(factory); // we reuse the same view QWidget parent; auto kdevToolView = makeQPointer(factory->create(&parent)); QCOMPARE(kdevToolView->parentWidget(), &parent); QCOMPARE(toolView->parentWidget(), kdevToolView.data()); // the children are kept alive when the tool view gets destroyed delete kdevToolView; QVERIFY(toolView); kdevToolView = factory->create(&parent); // and we reuse the ktexteditor tool view for the new kdevelop tool view QCOMPARE(toolView->parentWidget(), kdevToolView.data()); delete toolView; delete kdevToolView; delete plugin; QVERIFY(!findToolView(id)); } void TestKTextEditorPluginIntegration::testPlugin() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); auto view = makeQPointer(app->activeMainWindow()->pluginView(id)); QVERIFY(view); const auto rawView = view.data(); QSignalSpy spy(app->activeMainWindow(), &KTextEditor::MainWindow::pluginViewDeleted); QVERIFY(controller->unloadPlugin(id)); QVERIFY(!ktePlugin); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().count(), 2); QCOMPARE(spy.first().at(0), QVariant::fromValue(id)); QCOMPARE(spy.first().at(1), QVariant::fromValue(rawView)); QVERIFY(!view); } void TestKTextEditorPluginIntegration::testPluginUnload() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); delete ktePlugin; // don't crash plugin->unload(); } -QTEST_MAIN(TestKTextEditorPluginIntegration); +QTEST_MAIN(TestKTextEditorPluginIntegration) #include \ No newline at end of file diff --git a/kdevplatform/shell/watcheddocumentset.cpp b/kdevplatform/shell/watcheddocumentset.cpp index 890bbe54bc..b5f3a42ae6 100644 --- a/kdevplatform/shell/watcheddocumentset.cpp +++ b/kdevplatform/shell/watcheddocumentset.cpp @@ -1,361 +1,361 @@ /* * KDevelop Problem Reporter * * Copyright 2010 Dmitry Risenberg * * 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 "watcheddocumentset.h" #include #include #include #include #include #include #include #include #include #include namespace KDevelop { enum ActionFlag { DoUpdate = 1, DoEmit = 2 }; -Q_DECLARE_FLAGS(ActionFlags, ActionFlag); -Q_DECLARE_OPERATORS_FOR_FLAGS(ActionFlags); +Q_DECLARE_FLAGS(ActionFlags, ActionFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(ActionFlags) class WatchedDocumentSetPrivate : public QObject { Q_OBJECT public: using DocumentSet = WatchedDocumentSet::DocumentSet; explicit WatchedDocumentSetPrivate(WatchedDocumentSet* documentSet) : m_documentSet(documentSet) , m_showImports(false) { connect(DUChain::self(), &DUChain::updateReady, this, &WatchedDocumentSetPrivate::updateReady); } inline bool showImports() const { return m_showImports; } void setShowImports(bool showImports) { if (m_showImports == showImports) return; DocumentSet oldImports = m_imports; m_showImports = showImports; updateImports(); if (m_imports != oldImports) emit m_documentSet->changed(); } inline const DocumentSet& documents() const { return m_documents; } inline const DocumentSet& imports() const { return m_imports; } inline void doUpdate(ActionFlags flags) { if (flags.testFlag(DoUpdate)) updateImports(); if (flags.testFlag(DoEmit)) emit m_documentSet->changed(); } void setDocuments(const DocumentSet& docs, ActionFlags flags = {}) { m_documents = docs; doUpdate(flags); } void addDocument(const IndexedString& doc, ActionFlags flags = {}) { if (m_documents.contains(doc)) return; m_documents.insert(doc); doUpdate(flags); } void delDocument(const IndexedString& doc, ActionFlags flags = {}) { if (!m_documents.contains(doc)) return; m_documents.remove(doc); doUpdate(flags); } void updateImports() { if (!m_showImports) { if (!m_imports.isEmpty()) { m_imports.clear(); return; } return; } getImportsFromDUChain(); } private: void getImportsFromDU(TopDUContext* context, QSet& visitedContexts) { if (!context || visitedContexts.contains(context)) return; visitedContexts.insert(context); foreach (const DUContext::Import& ctx, context->importedParentContexts()) { TopDUContext* topCtx = dynamic_cast(ctx.context(nullptr)); if (topCtx) getImportsFromDU(topCtx, visitedContexts); } } void getImportsFromDUChain() { KDevelop::DUChainReadLocker lock; QSet visitedContexts; m_imports.clear(); foreach (const IndexedString& doc, m_documents) { TopDUContext* ctx = DUChain::self()->chainForDocument(doc); getImportsFromDU(ctx, visitedContexts); visitedContexts.remove(ctx); } foreach (TopDUContext* ctx, visitedContexts) { m_imports.insert(ctx->url()); } } void updateReady(const IndexedString& doc, const ReferencedTopDUContext&) { if (!m_showImports || !m_documents.contains(doc)) return; DocumentSet oldImports = m_imports; updateImports(); if (m_imports != oldImports) emit m_documentSet->changed(); } WatchedDocumentSet* m_documentSet; DocumentSet m_documents; DocumentSet m_imports; bool m_showImports; }; WatchedDocumentSet::WatchedDocumentSet(QObject* parent) : QObject(parent) , d(new WatchedDocumentSetPrivate(this)) { } WatchedDocumentSet::~WatchedDocumentSet() { } bool WatchedDocumentSet::showImports() const { return d->showImports(); } void WatchedDocumentSet::setShowImports(bool showImports) { d->setShowImports(showImports); } void WatchedDocumentSet::setCurrentDocument(const IndexedString&) { } WatchedDocumentSet::DocumentSet WatchedDocumentSet::get() const { return d->documents(); } WatchedDocumentSet::DocumentSet WatchedDocumentSet::getImports() const { return d->imports(); } CurrentDocumentSet::CurrentDocumentSet(const IndexedString& document, QObject* parent) : WatchedDocumentSet(parent) { d->setDocuments({document}, DoUpdate); } void CurrentDocumentSet::setCurrentDocument(const IndexedString& url) { d->setDocuments({url}, DoUpdate | DoEmit); } ProblemScope CurrentDocumentSet::getScope() const { return CurrentDocument; } OpenDocumentSet::OpenDocumentSet(QObject* parent) : WatchedDocumentSet(parent) { foreach (IDocument* doc, ICore::self()->documentController()->openDocuments()) { d->addDocument(IndexedString(doc->url())); } d->updateImports(); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &OpenDocumentSet::documentClosed); connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &OpenDocumentSet::documentCreated); } void OpenDocumentSet::documentClosed(IDocument* doc) { d->delDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } void OpenDocumentSet::documentCreated(IDocument* doc) { d->addDocument(IndexedString(doc->url()), DoUpdate | DoEmit); } ProblemScope OpenDocumentSet::getScope() const { return OpenDocuments; } ProjectSet::ProjectSet(QObject* parent) : WatchedDocumentSet(parent) { } void ProjectSet::fileAdded(ProjectFileItem* file) { d->addDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRemoved(ProjectFileItem* file) { d->delDocument(IndexedString(file->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::fileRenamed(const Path& oldFile, ProjectFileItem* newFile) { d->delDocument(IndexedString(oldFile.pathOrUrl())); d->addDocument(IndexedString(newFile->indexedPath()), DoUpdate | DoEmit); } void ProjectSet::trackProjectFiles(const IProject* project) { if (project) { // The implementation should derive from QObject somehow QObject* fileManager = dynamic_cast(project->projectFileManager()); if (fileManager) { // can't use new signal/slot syntax here, IProjectFileManager is no a QObject connect(fileManager, SIGNAL(fileAdded(ProjectFileItem*)), this, SLOT(fileAdded(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRemoved(ProjectFileItem*)), this, SLOT(fileRemoved(ProjectFileItem*))); connect(fileManager, SIGNAL(fileRenamed(Path,ProjectFileItem*)), this, SLOT(fileRenamed(Path,ProjectFileItem*))); } } } CurrentProjectSet::CurrentProjectSet(const IndexedString& document, QObject* parent) : ProjectSet(parent) , m_currentProject(nullptr) { setCurrentDocumentInternal(document); } void CurrentProjectSet::setCurrentDocument(const IndexedString& url) { setCurrentDocumentInternal(url); } void CurrentProjectSet::setCurrentDocumentInternal(const IndexedString& url) { IProject* projectForUrl = ICore::self()->projectController()->findProjectForUrl(url.toUrl()); if (projectForUrl && projectForUrl != m_currentProject) { m_currentProject = projectForUrl; d->setDocuments(m_currentProject->fileSet()); d->addDocument(IndexedString(m_currentProject->path().toLocalFile()), DoUpdate | DoEmit); trackProjectFiles(m_currentProject); } } ProblemScope CurrentProjectSet::getScope() const { return CurrentProject; } AllProjectSet::AllProjectSet(QObject* parent) : ProjectSet(parent) { foreach(const IProject* project, ICore::self()->projectController()->projects()) { foreach (const IndexedString &indexedString, project->fileSet()) { d->addDocument(indexedString); } d->addDocument(IndexedString(project->path().toLocalFile())); trackProjectFiles(project); } d->updateImports(); emit changed(); } ProblemScope AllProjectSet::getScope() const { return AllProjects; } BypassSet::BypassSet(QObject* parent) : WatchedDocumentSet(parent) { } ProblemScope BypassSet::getScope() const { return BypassScopeFilter; } } #include "watcheddocumentset.moc" diff --git a/kdevplatform/util/multilevellistview.cpp b/kdevplatform/util/multilevellistview.cpp index 2c966f5e64..b4b47f7795 100644 --- a/kdevplatform/util/multilevellistview.cpp +++ b/kdevplatform/util/multilevellistview.cpp @@ -1,463 +1,463 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multilevellistview.h" #include #include #include #include /** * Interface to set the label of a model. */ class LabeledProxy { public: virtual ~LabeledProxy() { } void setLabel(const QString& label) { m_label = label; } QVariant header(QAbstractItemModel* model, int section, Qt::Orientation orientation, int role) const { if (model && section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return m_label; } else { return QVariant(); } } protected: QString m_label; }; /** * The left-most view's model which only contains the root nodes of the source model. */ class RootProxyModel : public QSortFilterProxyModel, public LabeledProxy { Q_OBJECT public: explicit RootProxyModel( QObject* parent = nullptr ) : QSortFilterProxyModel( parent ) { } bool filterAcceptsRow( int /*source_row*/, const QModelIndex& source_parent ) const override { return !source_parent.isValid(); } QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } }; /** * A class that automatically updates its contents based on the selection in another view. */ class SubTreeProxyModel : public KSelectionProxyModel, public LabeledProxy { Q_OBJECT public: explicit SubTreeProxyModel( QItemSelectionModel* selectionModel, QObject* parent = nullptr ) : KSelectionProxyModel( selectionModel, parent ) {} QVariant headerData( int section, Qt::Orientation orientation, int role ) const override { return header(sourceModel(), section, orientation, role); } Qt::ItemFlags flags(const QModelIndex& index) const override { Qt::ItemFlags ret = KSelectionProxyModel::flags(index); if (filterBehavior() == KSelectionProxyModel::SubTreesWithoutRoots && hasChildren(index)) { // we want to select child items ret &= ~Qt::ItemIsSelectable; } return ret; } }; using namespace KDevelop; class KDevelop::MultiLevelListViewPrivate { public: explicit MultiLevelListViewPrivate(MultiLevelListView* view); ~MultiLevelListViewPrivate(); void viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void lastViewsContentsChanged(); void ensureViewSelected(QTreeView* view); /** * @param index index in any of our proxy models * @return an index in the source model */ QModelIndex mapToSource(QModelIndex index); /** * @param index an index in the source model * @return an index in the view's model at level @p level */ QModelIndex mapFromSource(QModelIndex index, int level); MultiLevelListView* view; int levels; QList views; QList proxies; QList layouts; QAbstractItemModel* model; }; MultiLevelListViewPrivate::MultiLevelListViewPrivate(MultiLevelListView* view_) : view(view_) , levels(0) , model(nullptr) { } MultiLevelListViewPrivate::~MultiLevelListViewPrivate() { } void MultiLevelListViewPrivate::viewSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (!current.isValid()) { // ignore, as we should always have some kind of selection return; } // figure out which proxy this signal belongs to QAbstractProxyModel* proxy = qobject_cast( const_cast(current.model())); Q_ASSERT(proxy); // what level is this proxy in int level = -1; for(int i = 0; i < levels; ++i) { if (views.at(i)->model() == proxy) { level = i; break; } } Q_ASSERT(level >= 0 && level < levels); if (level + 1 == levels) { // right-most view if (proxy->hasIndex(0, 0, current)) { // select the first leaf node for this view QModelIndex idx = current; QModelIndex child = proxy->index(0, 0, idx); while(child.isValid()) { idx = child; child = proxy->index(0, 0, idx); } views.last()->setCurrentIndex(idx); return; } // signal that our actual selection has changed emit view->currentIndexChanged(mapToSource(current), mapToSource(previous)); } else { // some leftish view // ensure the next view's first item is selected QTreeView* treeView = views.at(level + 1); // we need to delay the call, because at this point the child view // will still have its old data which is going to be invalidated // right after this method exits // be we must not set the index to 0,0 here directly, since e.g. // MultiLevelListView::setCurrentIndex might have been used, which // sets a proper index already. QMetaObject::invokeMethod(view, "ensureViewSelected", Qt::QueuedConnection, Q_ARG(QTreeView*, treeView)); } } void MultiLevelListViewPrivate::lastViewsContentsChanged() { views.last()->expandAll(); } void MultiLevelListViewPrivate::ensureViewSelected(QTreeView* view) { if (!view->currentIndex().isValid()) { view->setCurrentIndex(view->model()->index(0, 0)); } } QModelIndex MultiLevelListViewPrivate::mapToSource(QModelIndex index) { if (!index.isValid()) { return index; } while(index.model() != model) { QAbstractProxyModel* proxy = qobject_cast( const_cast(index.model())); Q_ASSERT(proxy); index = proxy->mapToSource(index); Q_ASSERT(index.isValid()); } return index; } QModelIndex MultiLevelListViewPrivate::mapFromSource(QModelIndex index, int level) { if (!index.isValid()) { return index; } Q_ASSERT(index.model() == model); QAbstractProxyModel* proxy = qobject_cast(views.at(level)->model()); Q_ASSERT(proxy); // find all proxies between the source and our view QVector proxies; proxies << proxy; forever { QAbstractProxyModel* child = qobject_cast(proxy->sourceModel()); if (child) { proxy = child; proxies << proxy; } else { Q_ASSERT(proxy->sourceModel() == model); break; } } // iterate in reverse order to find the view's index for(int i = proxies.size() - 1; i >= 0; --i) { proxy = proxies.at(i); index = proxy->mapFromSource(index); Q_ASSERT(index.isValid()); } return index; } MultiLevelListView::MultiLevelListView(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new MultiLevelListViewPrivate(this)) { setLayout(new QHBoxLayout()); layout()->setContentsMargins(0, 0, 0, 0); qRegisterMetaType("QTreeView*"); } MultiLevelListView::~MultiLevelListView() = default; int MultiLevelListView::levels() const { return d->levels; } void MultiLevelListView::setLevels(int levels) { qDeleteAll(d->views); qDeleteAll(d->proxies); qDeleteAll(d->layouts); d->views.clear(); d->proxies.clear(); d->layouts.clear(); d->levels = levels; QTreeView* previousView = nullptr; for (int i = 0; i < d->levels; ++i) { QVBoxLayout* levelLayout = new QVBoxLayout(); QTreeView* view = new QTreeView(this); view->setContentsMargins(0, 0, 0, 0); // only the right-most view is decorated view->setRootIsDecorated(i + 1 == d->levels); view->setHeaderHidden(false); view->setSelectionMode(QAbstractItemView::SingleSelection); if (!previousView) { // the root, i.e. left-most view RootProxyModel* root = new RootProxyModel(this); root->setDynamicSortFilter(true); d->proxies << root; root->setSourceModel(d->model); view->setModel(root); } else { SubTreeProxyModel* subTreeProxy = new SubTreeProxyModel(previousView->selectionModel(), this); if (i + 1 < d->levels) { // middel views only shows children of selection subTreeProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); } else { // right-most view shows the rest subTreeProxy->setFilterBehavior(KSelectionProxyModel::SubTreesWithoutRoots); } d->proxies << subTreeProxy; subTreeProxy->setSourceModel(d->model); // sorting requires another proxy in-between QSortFilterProxyModel* sortProxy = new QSortFilterProxyModel(subTreeProxy); sortProxy->setSourceModel(subTreeProxy); sortProxy->setDynamicSortFilter(true); view->setModel(sortProxy); } // view->setModel creates the selection model connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, [&] (const QModelIndex& current, const QModelIndex& previous) { d->viewSelectionChanged(current, previous); }); if (i + 1 == d->levels) { connect(view->model(), &QAbstractItemModel::rowsInserted, this, [&] { d->lastViewsContentsChanged(); }); } view->setSortingEnabled(true); view->sortByColumn(0, Qt::AscendingOrder); levelLayout->addWidget(view); layout()->addItem(levelLayout); d->layouts << levelLayout; d->views << view; previousView = view; } setModel(d->model); } QAbstractItemModel* MultiLevelListView::model() const { return d->model; } void MultiLevelListView::setModel(QAbstractItemModel* model) { d->model = model; foreach (LabeledProxy* proxy, d->proxies) { dynamic_cast(proxy)->setSourceModel(model); } if (model && !d->views.isEmpty()) { d->views.first()->setCurrentIndex(d->views.first()->model()->index(0, 0)); } } QTreeView* MultiLevelListView::viewForLevel( int level ) const { return d->views[level]; } void MultiLevelListView::addWidget(int level, QWidget* widget) { Q_ASSERT(level < d->levels); d->layouts[level]->addWidget(widget); } QModelIndex MultiLevelListView::currentIndex() const { return d->mapToSource(d->views.last()->currentIndex()); } void MultiLevelListView::setCurrentIndex(const QModelIndex& index) { // incoming index is for the original model Q_ASSERT(!index.isValid() || index.model() == d->model); const QModelIndex previous = currentIndex(); QModelIndex idx(index); QVector indexes; while (idx.isValid()) { indexes.prepend(idx); idx = idx.parent(); } for (int i = 0; i < d->levels; ++i) { QTreeView* view = d->views.at(i); if (indexes.size() <= i) { // select first item by default view->setCurrentIndex(view->model()->index(0, 0)); continue; } QModelIndex index; if (i + 1 == d->levels) { // select the very last index in the list (i.e. might be deep down in the actual tree) index = indexes.last(); } else { // select the first index for that level index = indexes.at(i); } view->setCurrentIndex(d->mapFromSource(index, i)); } emit currentIndexChanged(index, previous); } void MultiLevelListView::setRootIndex(const QModelIndex& index) { Q_ASSERT(!index.isValid() || index.model() == d->model); d->views.first()->setRootIndex(index); } void MultiLevelListView::setHeaderLabels(const QStringList& labels) { int n = qMin(d->levels, labels.size()); for (int i = 0; i < n; ++i) { d->proxies.at(i)->setLabel(labels[i]); } } static KSelectionProxyModel::FilterBehavior toSelectionProxyModelFilterBehavior(MultiLevelListView::LastLevelViewMode mode) { switch (mode) { case MultiLevelListView::SubTrees: return KSelectionProxyModel::SubTreesWithoutRoots; case MultiLevelListView::DirectChildren: return KSelectionProxyModel::ChildrenOfExactSelection; } Q_UNREACHABLE(); -}; +} void MultiLevelListView::setLastLevelViewMode(LastLevelViewMode mode) { if (d->proxies.isEmpty()) { return; } const auto filterBehavior = toSelectionProxyModelFilterBehavior(mode); dynamic_cast(d->proxies.last())->setFilterBehavior(filterBehavior); } #include "multilevellistview.moc" #include "moc_multilevellistview.cpp" diff --git a/kdevplatform/util/tests/test_environment.cpp b/kdevplatform/util/tests/test_environment.cpp index e6f2a6bac6..52cea2530f 100644 --- a/kdevplatform/util/tests/test_environment.cpp +++ b/kdevplatform/util/tests/test_environment.cpp @@ -1,82 +1,82 @@ /* * This file is part of KDevelop * * Copyright 2015 Artur Puzio * * 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 "test_environment.h" #include "util/environmentprofilelist.h" #include #include -QTEST_MAIN(TestEnvironment); +QTEST_MAIN(TestEnvironment) using ProcEnv = QMap; void TestEnvironment::testExpandVariables_data() { QTest::addColumn("env"); QTest::addColumn("expectedEnv"); QTest::newRow("no variables") << ProcEnv({}) << ProcEnv({}); QTest::newRow("simple variables") << ProcEnv{ {"VAR1","data"}, {"Var2","some other data"} } << ProcEnv({ {"VAR1","data"}, {"Var2","some other data"} }); QTest::newRow("PATH append and prepend") << ProcEnv({ {"PATH","/home/usr/bin:$PATH:/home/user/folder"} }) << ProcEnv({ {"PATH", "/home/usr/bin:/bin:/usr/bin:/home/user/folder"} }); QTest::newRow("\\$VAR") << ProcEnv({ {"MY_VAR","\\$PATH something \\$HOME"} }) << ProcEnv({ {"MY_VAR","$PATH something $HOME"} }); QTest::newRow("spaces, \\$VAR after $VAR") << ProcEnv({ {"MY_VAR","$PATH:$HOME something \\$HOME"} }) << ProcEnv({ {"MY_VAR","/bin:/usr/bin:/home/tom something $HOME"} }); QTest::newRow("VAR2=$VAR1") << ProcEnv({ {"VAR1","/some/path"},{"VAR2","$VAR1"} }) << ProcEnv({ {"VAR1","/some/path"},{"VAR2",""} }); } void TestEnvironment::testExpandVariables() { QFETCH(ProcEnv, env); QFETCH(ProcEnv, expectedEnv); QProcessEnvironment fakeSysEnv; fakeSysEnv.insert(QStringLiteral("PATH"),QStringLiteral("/bin:/usr/bin")); fakeSysEnv.insert(QStringLiteral("HOME"),QStringLiteral("/home/tom")); KDevelop::expandVariables(env, fakeSysEnv); for (auto it = expectedEnv.cbegin(); it!= expectedEnv.cend(); ++it) { QCOMPARE(env.value(it.key()), it.value()); } } diff --git a/kdevplatform/util/tests/test_executecompositejob.cpp b/kdevplatform/util/tests/test_executecompositejob.cpp index b03cba9c07..e4e9ab6ca3 100644 --- a/kdevplatform/util/tests/test_executecompositejob.cpp +++ b/kdevplatform/util/tests/test_executecompositejob.cpp @@ -1,118 +1,118 @@ /* * This file is part of KDevelop * Copyright 2014 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_executecompositejob.h" #include #include #include -QTEST_MAIN(TestExecuteCompositeJob); +QTEST_MAIN(TestExecuteCompositeJob) using namespace KDevelop; struct JobSpy { explicit JobSpy(KJob* job) : finished(job, SIGNAL(finished(KJob*))) , result(job, SIGNAL(result(KJob*))) {} QSignalSpy finished; QSignalSpy result; }; void TestExecuteCompositeJob::runOneJob() { QPointer slave(new TestJob); QPointer master(new ExecuteCompositeJob(nullptr, {slave.data()})); JobSpy masterSpy(master.data()); QSignalSpy startedSpy(slave.data(), SIGNAL(started(KJob*))); JobSpy slaveSpy(slave.data()); master->start(); QCOMPARE(startedSpy.count(), 1); QCOMPARE(slaveSpy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave->callEmitResult(); QCOMPARE(masterSpy.finished.count(), 1); QCOMPARE(masterSpy.result.count(), 1); QCOMPARE(startedSpy.count(), 1); QCOMPARE(slaveSpy.finished.count(), 1); QCOMPARE(slaveSpy.result.count(), 1); QTest::qWait(10); QVERIFY(!slave); QVERIFY(!master); } void TestExecuteCompositeJob::runTwoJobs() { QPointer slave1(new TestJob); QPointer slave2(new TestJob); QPointer master(new ExecuteCompositeJob(nullptr, {slave1.data(), slave2.data()})); JobSpy masterSpy(master.data()); QSignalSpy started1Spy(slave1.data(), SIGNAL(started(KJob*))); QSignalSpy started2Spy(slave2.data(), SIGNAL(started(KJob*))); JobSpy slave1Spy(slave1.data()); JobSpy slave2Spy(slave2.data()); master->start(); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 0); QCOMPARE(started2Spy.count(), 0); QCOMPARE(slave2Spy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave1->callEmitResult(); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 1); QCOMPARE(started2Spy.count(), 1); QCOMPARE(slave2Spy.finished.count(), 0); QCOMPARE(masterSpy.finished.count(), 0); slave2->callEmitResult(); QCOMPARE(masterSpy.finished.count(), 1); QCOMPARE(masterSpy.result.count(), 1); QCOMPARE(started1Spy.count(), 1); QCOMPARE(slave1Spy.finished.count(), 1); QCOMPARE(slave1Spy.result.count(), 1); QCOMPARE(started2Spy.count(), 1); QCOMPARE(slave2Spy.finished.count(), 1); QCOMPARE(slave2Spy.result.count(), 1); QTest::qWait(10); QVERIFY(!slave1); QVERIFY(!slave2); QVERIFY(!master); } diff --git a/kdevplatform/util/tests/test_objectlist.cpp b/kdevplatform/util/tests/test_objectlist.cpp index 8f8ea87721..c4d25d956e 100644 --- a/kdevplatform/util/tests/test_objectlist.cpp +++ b/kdevplatform/util/tests/test_objectlist.cpp @@ -1,82 +1,82 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_objectlist.h" #include "objectlist.h" #include -QTEST_MAIN(TestObjectList); +QTEST_MAIN(TestObjectList) using namespace KDevelop; void TestObjectList::testBasicInterface() { ObjectList list; QObject o; list.append(&o); QCOMPARE(list.data().size(), 1); QCOMPARE(list.data().at(0), &o); QCOMPARE(list.data().value(0), &o); QCOMPARE(list.data().size(), 1); QCOMPARE(list.data().at(0), &o); QVERIFY(list.remove(&o)); QCOMPARE(list.data().size(), 0); //QCOMPARE(list.at(0)), &o); // would crash QVERIFY(!list.data().value(0)); QVERIFY(list.data().isEmpty()); // try removing it again QVERIFY(!list.remove(&o)); } void TestObjectList::testDeleteAll() { ObjectList list; QPointer p(new QObject); list.append(p.data()); list.deleteAll(); QVERIFY(!p); } void TestObjectList::testBehaviorOnDestruction() { QPointer p(new QObject); { ObjectList list; list.append(p.data()); // nothing is supposed to happen on destruction } QVERIFY(p); { ObjectList list(ObjectListTracker::CleanupWhenDone); list.append(p.data()); } QVERIFY(!p); } diff --git a/kdevplatform/util/tests/test_path.cpp b/kdevplatform/util/tests/test_path.cpp index 1a29da7e1b..129664286d 100644 --- a/kdevplatform/util/tests/test_path.cpp +++ b/kdevplatform/util/tests/test_path.cpp @@ -1,622 +1,622 @@ /* * This file is part of KDevelop * Copyright 2012 Milian Wolff * Copyright 2015 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_path.h" #include #include #include -QTEST_MAIN(TestPath); +QTEST_MAIN(TestPath) using namespace KDevelop; static const int FILES_PER_FOLDER = 10; static const int FOLDERS_PER_FOLDER = 5; static const int TREE_DEPTH = 5; template T stringToUrl(const QString& path) { return T(path); } template<> QStringList stringToUrl(const QString& path) { return path.split('/'); } template T childUrl(const T& parent, const QString& child) { return T(parent, child); } template<> QStringList childUrl(const QStringList& parent, const QString& child) { QStringList ret = parent; ret << child; return ret; } template<> QUrl childUrl(const QUrl& parent, const QString& child) { QUrl ret = parent; ret.setPath(ret.path() + '/' + child); return ret; } template QVector generateData(const T& parent, int level) { QVector ret; // files per folder for (int i = 0; i < FILES_PER_FOLDER; ++i) { const QString fileName = QStringLiteral("file%1.txt").arg(i); const T file = childUrl(parent, fileName); Q_ASSERT(!ret.contains(file)); ret << file; } // nesting depth if (level < TREE_DEPTH) { // folders per folder for (int i = 0; i < FOLDERS_PER_FOLDER; ++i) { const QString folderName = QStringLiteral("folder%1").arg(i); const T folder = childUrl(parent, folderName); Q_ASSERT(!ret.contains(folder)); ret << folder; ret += generateData(folder, level + 1); } } return ret; } template void runBenchmark() { QBENCHMARK { const T base = stringToUrl("/tmp/foo/bar"); generateData(base, 0); } } void TestPath::bench_qurl() { runBenchmark(); } void TestPath::bench_qstringlist() { runBenchmark(); } void TestPath::bench_path() { runBenchmark(); } void TestPath::bench_fromLocalPath() { QFETCH(int, variant); const QString input(QStringLiteral("/foo/bar/asdf/bla/blub.h")); const int repeat = 1000; if (variant == 1) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(QUrl::fromLocalFile(input)); Q_UNUSED(path); } } } else if (variant == 2) { QBENCHMARK { for(int i = 0; i < repeat; ++i) { Path path = Path(input); Q_UNUSED(path); } } } else { QFAIL("unexpected variant"); } } void TestPath::bench_fromLocalPath_data() { QTest::addColumn("variant"); QTest::newRow("QUrl::fromLocalFile") << 1; QTest::newRow("QString") << 2; } void TestPath::bench_hash() { const Path path(QStringLiteral("/my/very/long/path/to/a/file.cpp")); QBENCHMARK { auto hash = qHash(path); Q_UNUSED(hash); } } /// Invoke @p op on URL @p base, but preserve drive letter if @p op removes it template QUrl preserveWindowsDriveLetter(const QUrl& base, Func op) { #ifndef Q_OS_WIN return op(base); #else // only apply to local files if (!base.isLocalFile()) { return op(base); } // save drive letter const QString windowsDriveLetter = base.toLocalFile().mid(0, 2); QUrl url = op(base); // restore drive letter if (url.toLocalFile().startsWith('/')) { url = QUrl::fromLocalFile(windowsDriveLetter + url.toLocalFile()); } return url; #endif } QUrl resolvedUrl(const QUrl& base, const QUrl& relative) { return preserveWindowsDriveLetter(base, [&](const QUrl& url) { return url.resolved(relative); }); } QUrl comparableUpUrl(const QUrl& url) { QUrl ret = preserveWindowsDriveLetter(url, [&](const QUrl& url) { return KIO::upUrl(url).adjusted(QUrl::RemovePassword); }); return ret.adjusted(QUrl::StripTrailingSlash); } void TestPath::testPath() { QFETCH(QString, input); QUrl url = QUrl::fromUserInput(input); url = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments); Path optUrl(input); if (!url.password().isEmpty()) { QUrl urlNoPass = url.adjusted(QUrl::RemovePassword); QCOMPARE(optUrl.toUrl(), urlNoPass); } else { QCOMPARE(optUrl.toUrl(), url); } QCOMPARE(optUrl.isLocalFile(), url.isLocalFile()); QCOMPARE(optUrl.pathOrUrl(), toUrlOrLocalFile(url, QUrl::RemovePassword)); QCOMPARE(optUrl.isValid(), url.isValid()); QCOMPARE(optUrl.isEmpty(), url.isEmpty()); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QCOMPARE(optUrl.toLocalFile(), url.toLocalFile()); QCOMPARE(optUrl, Path(input)); QCOMPARE(optUrl, Path(optUrl)); QVERIFY(optUrl != Path(input + "/asdf")); if (url.isLocalFile() && !input.startsWith(QLatin1String("file://"))) { QCOMPARE(optUrl, Path(QUrl::fromLocalFile(input))); } QCOMPARE(optUrl, Path(url)); if (url.isValid()) { QVERIFY(optUrl.relativePath(optUrl).isEmpty()); Path relativePath(optUrl, QStringLiteral("foo/bar")); QCOMPARE(optUrl.relativePath(relativePath), QLatin1String("foo/bar")); QCOMPARE(relativePath.relativePath(optUrl), QLatin1String("../../")); QVERIFY(optUrl.isParentOf(relativePath)); QVERIFY(!relativePath.isParentOf(optUrl)); #ifndef Q_OS_WIN Path absolutePath(optUrl, QStringLiteral("/laa/loo")); QCOMPARE(absolutePath.path(), QLatin1String("/laa/loo")); QCOMPARE(url.resolved(QUrl(QStringLiteral("/laa/loo"))).path(), QLatin1String("/laa/loo")); Path absolutePath2(optUrl, QStringLiteral("/")); QCOMPARE(absolutePath2.path(), QLatin1String("/")); QCOMPARE(url.resolved(QUrl(QStringLiteral("/"))).path(), QLatin1String("/")); #endif Path unrelatedPath(QStringLiteral("https://test@blubasdf.com:12345/")); QCOMPARE(optUrl.relativePath(unrelatedPath), unrelatedPath.pathOrUrl()); QCOMPARE(unrelatedPath.relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(!unrelatedPath.isParentOf(optUrl)); QVERIFY(!optUrl.isParentOf(unrelatedPath)); } QCOMPARE(Path().relativePath(optUrl), optUrl.pathOrUrl()); QVERIFY(optUrl.relativePath(Path()).isEmpty()); QVERIFY(Path().relativePath(Path()).isEmpty()); QVERIFY(!optUrl.isParentOf(Path())); QVERIFY(!Path().isParentOf(optUrl)); QVERIFY(!Path().isParentOf(Path())); QVERIFY(!optUrl.isParentOf(optUrl)); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.isLocalFile()); QCOMPARE(optUrl.isRemote(), optUrl.isValid() && !optUrl.remotePrefix().isEmpty()); if (url.path() == QLatin1String("/")) { url.setPath("/test/foo/bar"); } else { url.setPath(url.path() + "/test/foo/bar"); } if (url.scheme().isEmpty()) { url.setScheme(QStringLiteral("file")); } optUrl.addPath(QStringLiteral("test/foo/bar")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + "lalalala_adsf.txt"); optUrl.setLastPathSegment(QStringLiteral("lalalala_adsf.txt")); QCOMPARE(optUrl.lastPathSegment(), url.fileName()); QCOMPARE(optUrl.path(), url.isLocalFile() ? url.toLocalFile() : url.path()); QCOMPARE(optUrl.parent().toUrl(), comparableUpUrl(url)); QVERIFY(optUrl.parent().isDirectParentOf(optUrl)); QVERIFY(!optUrl.parent().parent().isDirectParentOf(optUrl)); #ifndef Q_OS_WIN Path a(QStringLiteral("/foo/bar/asdf/")); Path b(QStringLiteral("/foo/bar/")); QVERIFY(b.isDirectParentOf(a)); Path c(QStringLiteral("/foo/bar")); QVERIFY(c.isDirectParentOf(a)); #endif optUrl.clear(); url.clear(); QCOMPARE(optUrl.toUrl(), url); } void TestPath::testPath_data() { QTest::addColumn("input"); #ifndef Q_OS_WIN QTest::newRow("invalid") << ""; QTest::newRow("path") << "/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "/tmp/foo/asdf/"; QTest::newRow("root") << "/"; QTest::newRow("clean-path") << "/tmp/..///asdf/"; QTest::newRow("file") << "file:///tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///tmp/foo/bar/"; #else QTest::newRow("path") << "C:/tmp/foo/asdf.txt"; QTest::newRow("path-folder") << "C:/tmp/foo/asdf/"; QTest::newRow("root") << "C:/"; QTest::newRow("clean-path") << "C:/tmp/..///asdf/"; QTest::newRow("file") << "file:///C:/tmp/foo/asdf.txt"; QTest::newRow("file-folder") << "file:///C:/tmp/foo/bar/"; #endif QTest::newRow("remote-root") << "http://www.test.com/"; QTest::newRow("http") << "http://www.test.com/tmp/asdf.txt"; QTest::newRow("ftps") << "ftps://user@host.com/tmp/foo/asdf.txt"; QTest::newRow("password") << "ftps://user:password@host.com/tmp/asdf.txt"; QTest::newRow("port") << "http://localhost:8080/foo/bar/test.txt"; } void TestPath::testPathInvalid() { QFETCH(QString, input); Path url(input); QVERIFY(!url.isValid()); QVERIFY(url.isEmpty()); } void TestPath::testPathInvalid_data() { QTest::addColumn("input"); QTest::newRow("empty") << ""; QTest::newRow("fragment") << "http://test.com/#hello"; QTest::newRow("query") << "http://test.com/?hello"; QTest::newRow("suburl") << "file:///home/weis/kde.tgz#gzip:/#tar:/kdebase"; QTest::newRow("relative") << "../foo/bar"; QTest::newRow("name") << "asdfasdf"; QTest::newRow("remote-nopath") << "http://www.test.com"; } void TestPath::testPathOperators() { QFETCH(Path, left); QFETCH(Path, right); QFETCH(bool, equal); QFETCH(bool, less); bool greater = !equal && !less; QVERIFY(left == left); QVERIFY(right == right); QCOMPARE(left == right, equal); QCOMPARE(right == left, equal); QCOMPARE(left < right, less); QCOMPARE(left <= right, less || equal); QCOMPARE(left > right, greater); QCOMPARE(left >= right, greater || equal); QCOMPARE(right < left, greater); QCOMPARE(right <= left, greater || equal); QCOMPARE(right > left, less); QCOMPARE(right >= left, less || equal); } void TestPath::testPathOperators_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("equal"); QTest::addColumn("less"); Path a(QStringLiteral("/tmp/a")); Path b(QStringLiteral("/tmp/b")); Path c(QStringLiteral("/tmp/ac")); Path d(QStringLiteral("/d")); Path e(QStringLiteral("/tmp")); Path f(QStringLiteral("/tmp/")); Path invalid; QTest::newRow("a-b") << a << b << false << true; QTest::newRow("a-copy") << a << Path(a) << true << false; QTest::newRow("c-a") << c << a << false << false; QTest::newRow("c-invalid") << c << invalid << false << false; QTest::newRow("c-d") << c << d << false << false; QTest::newRow("e-f") << e << f << true << false; } void TestPath::testPathAddData() { QFETCH(QString, pathToAdd); const QStringList bases = { #ifndef Q_OS_WIN QStringLiteral("/"), QStringLiteral("/foo/bar/asdf/"), QStringLiteral("file:///foo/bar/asdf/"), #else "C:/", "C:/foo/bar/asdf/", "file:///C:/foo/bar/asdf/", #endif QStringLiteral("http://www.asdf.com/foo/bar/asdf/"), }; foreach(const QString& base, bases) { QUrl baseUrl = QUrl::fromUserInput(base); if (QDir::isRelativePath(pathToAdd)) { baseUrl = resolvedUrl(baseUrl, QUrl(pathToAdd)); } else if (QDir::isRelativePath(pathToAdd) || baseUrl.path() != QLatin1String("/")) { // if pathToAdd == /absolute && baseUrl == "/", below call would lead to an invalid QUrl // with qtbase.git/f62768d046528636789f901ac79e2cfa1843a7b7 baseUrl.setPath(baseUrl.path() + pathToAdd); } else { baseUrl.setPath(pathToAdd); } baseUrl = baseUrl.adjusted(QUrl::NormalizePathSegments); // QUrl::StripTrailingSlash converts file:/// to file: which is not what we want if (baseUrl.path() != QLatin1String("/")) { baseUrl = baseUrl.adjusted(QUrl::StripTrailingSlash); } Path basePath(base); basePath.addPath(pathToAdd); QCOMPARE(basePath.pathOrUrl(), toUrlOrLocalFile(baseUrl)); QCOMPARE(basePath.toUrl(), baseUrl); } } void TestPath::testPathAddData_data() { QTest::addColumn("pathToAdd"); const QStringList paths = QStringList() << QStringLiteral("file.txt") << QStringLiteral("path/file.txt") << QStringLiteral("path//file.txt") << QStringLiteral("/absolute") << QStringLiteral("../") << QStringLiteral("..") << QStringLiteral("../../../") << QStringLiteral("./foo") << QStringLiteral("../relative") << QStringLiteral("../../relative") << QStringLiteral("../foo/../bar") << QStringLiteral("../foo/./bar") << QStringLiteral("../../../../../../../invalid"); foreach(const QString &path, paths) { QTest::newRow(qstrdup(path.toUtf8().constData())) << path; } } void TestPath::testPathBaseCtor() { QFETCH(QString, base); QFETCH(QString, subPath); QFETCH(QString, expected); const Path basePath(base); const Path path(basePath, subPath); QCOMPARE(path.pathOrUrl(), expected); } void TestPath::testPathBaseCtor_data() { QTest::addColumn("base"); QTest::addColumn("subPath"); QTest::addColumn("expected"); QTest::newRow("empty") << "" << "" << ""; QTest::newRow("empty-relative") << "" << "foo" << ""; #ifndef Q_OS_WIN QTest::newRow("root-empty") << "/" << "" << "/"; QTest::newRow("root-root") << "/" << "/" << "/"; QTest::newRow("root-relative") << "/" << "bar" << "/bar"; QTest::newRow("root-relative-dirty") << "/" << "bar//foo/a/.." << "/bar/foo"; QTest::newRow("path-relative") << "/foo/bar" << "bar/foo" << "/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "/foo/bar" << "/bar/foo" << "/bar/foo"; #else QTest::newRow("root-empty") << "C:/" << "" << "C:"; QTest::newRow("root1-empty") << "C:" << "" << "C:"; QTest::newRow("root-root") << "C:/" << "C:/" << "C:"; QTest::newRow("root-relative") << "C:/" << "bar" << "C:/bar"; QTest::newRow("root1-relative") << "C:" << "bar" << "C:/bar"; QTest::newRow("root-relative-dirty") << "C:/" << "bar//foo/a/.." << "C:/bar/foo"; QTest::newRow("path-relative") << "C:/foo/bar" << "bar/foo" << "C:/foo/bar/bar/foo"; QTest::newRow("path-absolute") << "C:/foo/bar" << "C:/bar/foo" << "C:/bar/foo"; #endif QTest::newRow("remote-path-absolute") << "http://foo.com/foo/bar" << "/bar/foo" << "http://foo.com/bar/foo"; QTest::newRow("remote-path-relative") << "http://foo.com/foo/bar" << "bar/foo" << "http://foo.com/foo/bar/bar/foo"; } // there is no cd() in QUrl, emulate what KUrl did static bool cdQUrl(QUrl& url, const QString& path) { if (path.isEmpty() || !url.isValid()) { return false; } // have to append slash otherwise last segment is treated as a file name and not a directory if (!url.path().endsWith('/')) { url.setPath(url.path() + '/'); } url = url.resolved(QUrl(path)).adjusted(QUrl::RemoveFragment | QUrl::RemoveQuery); return true; } void TestPath::testPathCd() { QFETCH(QString, base); QFETCH(QString, change); Path path = base.isEmpty() ? Path() : Path(base); QUrl url = QUrl::fromUserInput(base); Path changed = path.cd(change); if (cdQUrl(url, change)) { QVERIFY(changed.isValid()); } url = url.adjusted(QUrl::NormalizePathSegments); QCOMPARE(changed.pathOrUrl(), toUrlOrLocalFile(url, QUrl::StripTrailingSlash)); } void TestPath::testPathCd_data() { QTest::addColumn("base"); QTest::addColumn("change"); const QVector bases{ QLatin1String(""), #ifndef Q_OS_WIN QStringLiteral("/foo"), QStringLiteral("/foo/bar/asdf"), #else "C:/foo", "C:/foo/bar/asdf", #endif QStringLiteral("http://foo.com/"), QStringLiteral("http://foo.com/foo"), QStringLiteral("http://foo.com/foo/bar/asdf") }; foreach (const QString& base, bases) { QTest::newRow(qstrdup(qPrintable(base + "-"))) << base << ""; QTest::newRow(qstrdup(qPrintable(base + "-.."))) << base << ".."; QTest::newRow(qstrdup(qPrintable(base + "-../"))) << base << "../"; QTest::newRow(qstrdup(qPrintable(base + "v../foo"))) << base << "../foo"; QTest::newRow(qstrdup(qPrintable(base + "-."))) << base << "."; QTest::newRow(qstrdup(qPrintable(base + "-./"))) << base << "./"; QTest::newRow(qstrdup(qPrintable(base + "-./foo"))) << base << "./foo"; QTest::newRow(qstrdup(qPrintable(base + "-./foo/bar"))) << base << "./foo/bar"; QTest::newRow(qstrdup(qPrintable(base + "-foo/.."))) << base << "foo/.."; QTest::newRow(qstrdup(qPrintable(base + "-foo/"))) << base << "foo/"; QTest::newRow(qstrdup(qPrintable(base + "-foo/../bar"))) << base << "foo/../bar"; #ifdef Q_OS_WIN if (!base.startsWith("C:/") ) { // only add next rows for remote URLs on Windows #endif QTest::newRow(qstrdup(qPrintable(base + "-/foo"))) << base << "/foo"; QTest::newRow(qstrdup(qPrintable(base + "-/foo/../bar"))) << base << "/foo/../bar"; #ifdef Q_OS_WIN } #endif } } void TestPath::testHasParent_data() { QTest::addColumn("input"); QTest::addColumn("hasParent"); QTest::newRow("empty") << QString() << false; #ifdef Q_OS_WIN QTest::newRow("\\") << QStringLiteral("\\") << true; // true b/c parent could be e.g. 'C:' #else QTest::newRow("/") << QStringLiteral("/") << false; QTest::newRow("/foo") << QStringLiteral("/foo") << true; QTest::newRow("/foo/bar") << QStringLiteral("/foo/bar") << true; QTest::newRow("//foo/bar") << QStringLiteral("//foo/bar") << true; #endif QTest::newRow("http://foo.bar") << QStringLiteral("http://foo.bar") << false; QTest::newRow("http://foo.bar/") << QStringLiteral("http://foo.bar/") << false; QTest::newRow("http://foo.bar/asdf") << QStringLiteral("http://foo.bar/asdf") << true; QTest::newRow("http://foo.bar/asdf/asdf") << QStringLiteral("http://foo.bar/asdf/asdf") << true; } void TestPath::testHasParent() { QFETCH(QString, input); Path path(input); QTEST(path.hasParent(), "hasParent"); } void TestPath::QUrl_acceptance() { const QUrl baseLocal = QUrl(QStringLiteral("file:///foo.h")); QCOMPARE(baseLocal.isValid(), true); QCOMPARE(baseLocal.isRelative(), false); QCOMPARE(baseLocal, QUrl::fromLocalFile(QStringLiteral("/foo.h"))); QCOMPARE(baseLocal, QUrl::fromUserInput(QStringLiteral("/foo.h"))); QUrl relative(QStringLiteral("bar.h")); QCOMPARE(relative.isRelative(), true); QCOMPARE(baseLocal.resolved(relative), QUrl(QStringLiteral("file:///bar.h"))); QUrl relative2(QStringLiteral("/foo/bar.h")); QCOMPARE(relative2.isRelative(), true); QCOMPARE(baseLocal.resolved(relative2), QUrl(QStringLiteral("file:///foo/bar.h"))); const QUrl baseRemote = QUrl(QStringLiteral("http://foo.org/asdf/foo/asdf")); QCOMPARE(baseRemote.resolved(relative), QUrl(QStringLiteral("http://foo.org/asdf/foo/bar.h"))); } diff --git a/kdevplatform/util/tests/test_stringhandler.cpp b/kdevplatform/util/tests/test_stringhandler.cpp index 4ab15151b5..1768fbb7d6 100644 --- a/kdevplatform/util/tests/test_stringhandler.cpp +++ b/kdevplatform/util/tests/test_stringhandler.cpp @@ -1,103 +1,103 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_stringhandler.h" #include "kdevstringhandler.h" #include -QTEST_MAIN(TestStringHandler); +QTEST_MAIN(TestStringHandler) using namespace KDevelop; Q_DECLARE_METATYPE(HtmlToPlainTextMode) void TestStringHandler::testHtmlToPlainText() { QFETCH(QString, html); QFETCH(HtmlToPlainTextMode, mode); QFETCH(QString, expectedPlainText); QString plainText = htmlToPlainText(html, mode); QCOMPARE(plainText, expectedPlainText); } void TestStringHandler::testHtmlToPlainText_data() { QTest::addColumn("html"); QTest::addColumn("mode"); QTest::addColumn("expectedPlainText"); QTest::newRow("simple-fast") << "

bar()

a
foo
" << KDevelop::FastMode << "bar() a foo"; QTest::newRow("simple-complete") << "

bar()

a
foo
" << KDevelop::CompleteMode << "bar() \na\nfoo"; } void TestStringHandler::testStripAnsiSequences() { QFETCH(QString, input); QFETCH(QString, expectedOutput); const auto output = stripAnsiSequences(input); QCOMPARE(output, expectedOutput); } void TestStringHandler::testStripAnsiSequences_data() { QTest::addColumn("input"); QTest::addColumn("expectedOutput"); QTest::newRow("simple") << QStringLiteral("foo bar:") << "foo bar:"; } void TestStringHandler::testNormalizeLineEndings() { QFETCH(QByteArray, text); QFETCH(QByteArray, expectedOutput); normalizeLineEndings(text); QCOMPARE(text, expectedOutput); } void TestStringHandler::testNormalizeLineEndings_data() { QTest::addColumn("text"); QTest::addColumn("expectedOutput"); QTest::newRow("trivial") << QByteArray("foo\nbar\n") << QByteArray("foo\nbar\n"); QTest::newRow("dos") << QByteArray("foo\r\nbar\r\n") << QByteArray("foo\nbar\n"); QTest::newRow("macos_classic") << QByteArray("foo\rbar\r") << QByteArray("foo\nbar\n"); QTest::newRow("mess") << QByteArray("\r\n\n\r\r\r\n\r") << QByteArray("\n\n\n\n\n\n"); } diff --git a/kdevplatform/util/tests/test_texteditorhelpers.cpp b/kdevplatform/util/tests/test_texteditorhelpers.cpp index 4bdd8c9649..ae70f8e268 100644 --- a/kdevplatform/util/tests/test_texteditorhelpers.cpp +++ b/kdevplatform/util/tests/test_texteditorhelpers.cpp @@ -1,92 +1,92 @@ /* * Copyright 2016 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_texteditorhelpers.h" #include "texteditorhelpers.h" #include -QTEST_MAIN(TestKTextEditorHelpers); +QTEST_MAIN(TestKTextEditorHelpers) using namespace KDevelop; void TestKTextEditorHelpers::testExtractCursor() { QFETCH(QString, input); QFETCH(KTextEditor::Cursor, expectedCursor); QFETCH(QString, expectedPath); int pathLen; const auto cursor = KTextEditorHelpers::extractCursor(input, &pathLen); QCOMPARE(cursor, expectedCursor); QCOMPARE(input.mid(0, pathLen), expectedPath); } void TestKTextEditorHelpers::testExtractCursor_data() { QTest::addColumn("input"); QTest::addColumn("expectedCursor"); QTest::addColumn("expectedPath"); // valid input QTest::newRow("file") << QStringLiteral("widget.cpp") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp:12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line:column") << QStringLiteral("widget.cpp:12:5") << KTextEditor::Cursor(11, 4) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#L12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); QTest::newRow("file:line") << QStringLiteral("widget.cpp#n12") << KTextEditor::Cursor(11, 0) << QStringLiteral("widget.cpp"); // partially invalid input QTest::newRow("file:") << QStringLiteral("widget.cpp:") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp:"); QTest::newRow("file:") << QStringLiteral("widget.cpp#") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#"); QTest::newRow("file:") << QStringLiteral("widget.cpp#L") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#L"); QTest::newRow("file:") << QStringLiteral("widget.cpp#n") << KTextEditor::Cursor::invalid() << QStringLiteral("widget.cpp#n"); } diff --git a/kdevplatform/util/widgetcolorizer.h b/kdevplatform/util/widgetcolorizer.h index 837f39bb59..8a006c948b 100644 --- a/kdevplatform/util/widgetcolorizer.h +++ b/kdevplatform/util/widgetcolorizer.h @@ -1,89 +1,89 @@ /* * Copyright 2015 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #pragma once #include #include class QColor; class QModelIndex; class QPainter; class QRect; class QPalette; class QTreeView; namespace KDevelop { namespace WidgetColorizer { /** * Generate a new color by blending the input @p color with the foreground. * * This function also works nicely on dark color schemes, in contrast to * simply setting an alpha channel value on the color. * * @p color Input color to blend. * @p ratio Ratio to decide how strong to do the blending. * When set to 0 you get the foreground color as-is, when set to 1 * you get the input color as-is. */ KDEVPLATFORMUTIL_EXPORT QColor blendForeground(QColor color, float ratio, const QColor& foreground, const QColor& background); /** * Generate a new color by blending the input @p color with the background. * * This function also works nicely on dark color schemes, in contrast to * simply setting an alpha channel value on the color. * * @p color Input color to blend. * @p ratio Ratio to decide how strong to do the blending. * When set to 0 you get the background color as-is, when set to 1 * you get the input color as-is. */ KDEVPLATFORMUTIL_EXPORT QColor blendBackground(const QColor& color, float ratio, const QColor& foreground, const QColor& background); KDEVPLATFORMUTIL_EXPORT void drawBranches(const QTreeView* treeView, QPainter* painter, const QRect& rect, const QModelIndex& index, const QColor& color); /** * Return a random color fit for the active palette. * * @p id An id which can be generated e.g. by qHash. Same ids will return * the same color. * @p activePalette The palette to use for generating the color. * @p background If set to true, a background color will be returned, * otherwise a foreground color will be returned by default. */ KDEVPLATFORMUTIL_EXPORT QColor colorForId(uint id, const QPalette& activePalette, bool background = false); /** * Returns true when the setting is enabled to colorize widgets representing * files belonging to projects. */ KDEVPLATFORMUTIL_EXPORT bool colorizeByProject(); -}; +} } diff --git a/kdevplatform/vcs/interfaces/icontentawareversioncontrol.cpp b/kdevplatform/vcs/interfaces/icontentawareversioncontrol.cpp index 2b76ad9958..92d2511fb4 100644 --- a/kdevplatform/vcs/interfaces/icontentawareversioncontrol.cpp +++ b/kdevplatform/vcs/interfaces/icontentawareversioncontrol.cpp @@ -1,55 +1,55 @@ /* This file is part of KDevelop * * Copyright 2013 Sven Brauch * * 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 "icontentawareversioncontrol.h" namespace KDevelop { class CheckInRepositoryJobPrivate { explicit CheckInRepositoryJobPrivate(KTextEditor::Document* document) : document(document) { }; friend class CheckInRepositoryJob; KTextEditor::Document* document; }; CheckInRepositoryJob::CheckInRepositoryJob(KTextEditor::Document* document) : KJob() , d(new CheckInRepositoryJobPrivate(document)) { connect(this, &CheckInRepositoryJob::finished, this, &CheckInRepositoryJob::deleteLater); setCapabilities(Killable); -}; +} CheckInRepositoryJob::~CheckInRepositoryJob() = default; KTextEditor::Document* CheckInRepositoryJob::document() const { return d->document; } void CheckInRepositoryJob::abort() { kill(); } } // namespace KDevelop diff --git a/kdevplatform/vcs/models/tests/test_models.cpp b/kdevplatform/vcs/models/tests/test_models.cpp index 6cab21223d..cc4c43f2e8 100644 --- a/kdevplatform/vcs/models/tests/test_models.cpp +++ b/kdevplatform/vcs/models/tests/test_models.cpp @@ -1,142 +1,142 @@ /*************************************************************************** * Copyright 2011 Andrey Batyiev * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "test_models.h" #include #include #include #include using namespace KDevelop; void TestModels::initTestCase() { AutoTestShell::init({QStringLiteral("dummy")}); TestCore::initialize(); } void TestModels::cleanupTestCase() { TestCore::shutdown(); } void TestModels::testVcsFileChangesModel() { const auto indexForUrl = [](const VcsFileChangesModel* model, const QUrl& url) { return model->match(model->index(0, 0), VcsFileChangesModel::UrlRole, url, 1, Qt::MatchExactly).value(0); }; const auto statusInfo = [](const QModelIndex& idx) { return idx.data(VcsFileChangesModel::VcsStatusInfoRole).value(); }; QScopedPointer model(new VcsFileChangesModel); // Newly created model should be empty QVERIFY(model->rowCount() == 0); // Pull some files into QUrl filenames[] = { QUrl::fromLocalFile(QStringLiteral("foo")), QUrl::fromLocalFile(QStringLiteral("bar")), QUrl::fromLocalFile(QStringLiteral("pew")), QUrl::fromLocalFile(QStringLiteral("trash")) }; VcsStatusInfo::State states[] = {VcsStatusInfo::ItemAdded, VcsStatusInfo::ItemModified, VcsStatusInfo::ItemDeleted, VcsStatusInfo::ItemUpToDate}; VcsStatusInfo status; for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), (i+1)); } // Pulling up-to-date file doesn't change anything { status.setUrl(filenames[3]); status.setState(states[3]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex idx = indexForUrl(model.data(), filenames[i]); QVERIFY(idx.isValid()); VcsStatusInfo info = statusInfo(idx); QVERIFY(info.url().isValid()); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Pull it all again = nothing changed for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); QCOMPARE(model->rowCount(), 3); } // Check that all OK for(int i = 0; i < 3; i++) { QModelIndex item = indexForUrl(model.data(), filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } // Remove one file { states[1] = VcsStatusInfo::ItemUpToDate; status.setUrl(filenames[1]); status.setState(states[1]); model->updateState(status); QCOMPARE(model->rowCount(), 2); } // Check them all for(int i = 0; i < 3; i++) { if(states[i] != VcsStatusInfo::ItemUpToDate && states[i] != VcsStatusInfo::ItemUnknown) { QModelIndex item = indexForUrl(model.data(), filenames[i]); QVERIFY(item.isValid()); VcsStatusInfo info = statusInfo(item); QCOMPARE(info.url(), filenames[i]); QCOMPARE(info.state(), states[i]); } } // Delete them all model->removeRows(0, model->rowCount()); QCOMPARE(model->rowCount(), 0); // Pull it all again for(int i = 0; i < 3; i++) { status.setUrl(filenames[i]); status.setState(states[i]); model->updateState(status); } QCOMPARE(model->rowCount(), 2); } -QTEST_MAIN(TestModels); +QTEST_MAIN(TestModels) diff --git a/kdevplatform/vcs/tests/test_vcsannotation.cpp b/kdevplatform/vcs/tests/test_vcsannotation.cpp index 806a144600..ba22265a3c 100644 --- a/kdevplatform/vcs/tests/test_vcsannotation.cpp +++ b/kdevplatform/vcs/tests/test_vcsannotation.cpp @@ -1,148 +1,148 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsannotation.h" #include #include #include using namespace KDevelop; static VcsAnnotationLine createAnnotationLine(int lineNumber, const QString& text, const QString& author, const VcsRevision& revision, const QDateTime& date, const QString& commitMessage) { VcsAnnotationLine annotationLine; annotationLine.setLineNumber(lineNumber); annotationLine.setText(text); annotationLine.setAuthor(author); annotationLine.setRevision(revision); annotationLine.setDate(date); annotationLine.setCommitMessage(commitMessage); return annotationLine; } void TestVcsAnnotation::testCopyConstructor() { const int lineNumber = 1; const QString text("Text A"); const QString author("Author A"); VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString commitMessage("Commit A"); const VcsAnnotationLine annotationLine = createAnnotationLine(lineNumber, text, author, revision, date, commitMessage); const QUrl location(QStringLiteral("git://foo")); // test plain copy { VcsAnnotation annotationA; annotationA.setLocation(location); annotationA.insertLine(lineNumber, annotationLine); VcsAnnotation annotationB(annotationA); QCOMPARE(annotationA.location(), location); QCOMPARE(annotationA.lineCount(), 1); QVERIFY(annotationA.containsLine(lineNumber)); QCOMPARE(annotationB.location(), location); QCOMPARE(annotationB.lineCount(), 1); QVERIFY(annotationB.containsLine(lineNumber)); } const QUrl locationNew(QStringLiteral("svn://bar")); // test detach after changing A { VcsAnnotation annotationA; annotationA.setLocation(location); annotationA.insertLine(lineNumber, annotationLine); VcsAnnotation annotationB(annotationA); // change a property of A annotationA.setLocation(locationNew); QCOMPARE(annotationA.location(), locationNew); QCOMPARE(annotationA.lineCount(), 1); QVERIFY(annotationA.containsLine(lineNumber)); QCOMPARE(annotationB.location(), location); QCOMPARE(annotationB.lineCount(), 1); QVERIFY(annotationB.containsLine(lineNumber)); } } void TestVcsAnnotation::testAssignOperator() { const int lineNumber = 1; const QString text("Text A"); const QString author("Author A"); VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString commitMessage("Commit A"); const VcsAnnotationLine annotationLine = createAnnotationLine(lineNumber, text, author, revision, date, commitMessage); const QUrl location(QStringLiteral("http://kdevelop.org")); // test plain copy { VcsAnnotation annotationA; annotationA.setLocation(location); annotationA.insertLine(lineNumber, annotationLine); VcsAnnotation annotationB; annotationB = annotationA; QCOMPARE(annotationA.location(), location); QCOMPARE(annotationA.lineCount(), 1); QVERIFY(annotationA.containsLine(lineNumber)); QCOMPARE(annotationB.location(), location); QCOMPARE(annotationB.lineCount(), 1); QVERIFY(annotationB.containsLine(lineNumber)); } const QUrl locationNew(QStringLiteral("http://kate-editor.org")); // test detach after changing A { VcsAnnotation annotationA; annotationA.setLocation(location); annotationA.insertLine(lineNumber, annotationLine); VcsAnnotation annotationB; annotationB = annotationA; // change a property of A annotationA.setLocation(locationNew); QCOMPARE(annotationA.location(), locationNew); QCOMPARE(annotationA.lineCount(), 1); QVERIFY(annotationA.containsLine(lineNumber)); QCOMPARE(annotationB.location(), location); QCOMPARE(annotationB.lineCount(), 1); QVERIFY(annotationB.containsLine(lineNumber)); } } -QTEST_GUILESS_MAIN(TestVcsAnnotation); +QTEST_GUILESS_MAIN(TestVcsAnnotation) diff --git a/kdevplatform/vcs/tests/test_vcsannotationline.cpp b/kdevplatform/vcs/tests/test_vcsannotationline.cpp index 421975c523..28cb0fbbee 100644 --- a/kdevplatform/vcs/tests/test_vcsannotationline.cpp +++ b/kdevplatform/vcs/tests/test_vcsannotationline.cpp @@ -1,173 +1,173 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsannotationline.h" #include #include #include using namespace KDevelop; void TestVcsAnnotationLine::setAnnotationLine(VcsAnnotationLine& annotationLine, int lineNumber, const QString& text, const QString& author, const VcsRevision& revision, const QDateTime& date, const QString& commitMessage) { annotationLine.setLineNumber(lineNumber); annotationLine.setText(text); annotationLine.setAuthor(author); annotationLine.setRevision(revision); annotationLine.setDate(date); annotationLine.setCommitMessage(commitMessage); } void TestVcsAnnotationLine::compareAnnotationLine(const VcsAnnotationLine& annotationLine, int lineNumber, const QString& text, const QString& author, const VcsRevision& revision, const QDateTime& date, const QString& commitMessage) { QCOMPARE(annotationLine.lineNumber(), lineNumber); QCOMPARE(annotationLine.text(), text); QCOMPARE(annotationLine.author(), author); QCOMPARE(annotationLine.revision(), revision); QCOMPARE(annotationLine.date(), date); QCOMPARE(annotationLine.commitMessage(), commitMessage); } void TestVcsAnnotationLine::testCopyConstructor() { // copy invalid { VcsAnnotationLine annotationLineA; VcsAnnotationLine annotationLineB(annotationLineA); QCOMPARE(annotationLineA.revision().revisionType(), VcsRevision::Invalid); QCOMPARE(annotationLineB.revision().revisionType(), VcsRevision::Invalid); } // test plain copy const int lineNumber = 1; const QString text("Text A"); const QString author("Author A"); VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString commitMessage("Commit A"); { VcsAnnotationLine annotationLineA; setAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); VcsAnnotationLine annotationLineB(annotationLineA); compareAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); compareAnnotationLine(annotationLineB, lineNumber, text, author, revision, date, commitMessage); } const int lineNumberNew = 10; // test detach after changing A { VcsAnnotationLine annotationLineA; setAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); VcsAnnotationLine annotationLineB(annotationLineA); // change a property of A annotationLineA.setLineNumber(lineNumberNew); compareAnnotationLine(annotationLineA, lineNumberNew, text, author, revision, date, commitMessage); compareAnnotationLine(annotationLineB, lineNumber, text, author, revision, date, commitMessage); } } void TestVcsAnnotationLine::testAssignOperator() { // assign invalid { VcsAnnotationLine annotationLineA; VcsAnnotationLine annotationLineB; VcsRevision revision; revision.setRevisionValue(2, VcsRevision::FileNumber); annotationLineB.setRevision(revision); annotationLineB = annotationLineA; QCOMPARE(annotationLineA.revision().revisionType(), VcsRevision::Invalid); QCOMPARE(annotationLineB.revision().revisionType(), VcsRevision::Invalid); } // test plain assign const int lineNumber = 1; const QString text("Text A"); const QString author("Author A"); VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString commitMessage("Commit A"); { VcsAnnotationLine annotationLineA; setAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); VcsAnnotationLine annotationLineB; annotationLineB = annotationLineA; compareAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); compareAnnotationLine(annotationLineB, lineNumber, text, author, revision, date, commitMessage); } const int lineNumberNew = 10; // test detach after changing A { VcsAnnotationLine annotationLineA; setAnnotationLine(annotationLineA, lineNumber, text, author, revision, date, commitMessage); VcsAnnotationLine annotationLineB; annotationLineB = annotationLineA; // change a property of A annotationLineA.setLineNumber(lineNumberNew); compareAnnotationLine(annotationLineA, lineNumberNew, text, author, revision, date, commitMessage); compareAnnotationLine(annotationLineB, lineNumber, text, author, revision, date, commitMessage); } } -QTEST_GUILESS_MAIN(TestVcsAnnotationLine); +QTEST_GUILESS_MAIN(TestVcsAnnotationLine) diff --git a/kdevplatform/vcs/tests/test_vcsdiff.cpp b/kdevplatform/vcs/tests/test_vcsdiff.cpp index abfac8db5a..7da1e24b7a 100644 --- a/kdevplatform/vcs/tests/test_vcsdiff.cpp +++ b/kdevplatform/vcs/tests/test_vcsdiff.cpp @@ -1,129 +1,129 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsdiff.h" #include #include using namespace KDevelop; void TestVcsDiff::setDiff(VcsDiff& diff, const QString& diffString, const QUrl& baseDiff, uint depth) { diff.setDiff(diffString); diff.setBaseDiff(baseDiff); diff.setDepth(depth); } void TestVcsDiff::compareDiff(const VcsDiff& diff, const QString& diffString, const QUrl& baseDiff, uint depth) { QCOMPARE(diff.diff(), diffString); QCOMPARE(diff.baseDiff(), baseDiff); QCOMPARE(diff.depth(), depth); } void TestVcsDiff::testCopyConstructor() { // test plain copy const QString diffString("diff"); const QUrl baseDiff("git://1"); const uint depth = 1; const VcsLocation location("server"); { VcsDiff diffA; setDiff(diffA, diffString, baseDiff, depth); VcsDiff diffB(diffA); compareDiff(diffA, diffString, baseDiff, depth); compareDiff(diffB, diffString, baseDiff, depth); } const QString diffStringNew("diffNew"); // test detach after changing A { VcsDiff diffA; setDiff(diffA, diffString, baseDiff, depth); VcsDiff diffB(diffA); // change a property of A diffA.setDiff(diffStringNew); compareDiff(diffA, diffStringNew, baseDiff, depth); compareDiff(diffB, diffString, baseDiff, depth); } } void TestVcsDiff::testAssignOperator() { // test plain copy const QString diffString("diff"); const QUrl baseDiff("git://1"); const uint depth = 1; const VcsLocation location("server"); { VcsDiff diffA; setDiff(diffA, diffString, baseDiff, depth); VcsDiff diffB; diffB = diffA; compareDiff(diffA, diffString, baseDiff, depth); compareDiff(diffB, diffString, baseDiff, depth); } const QString diffStringNew("diffNew"); // test detach after changing A { VcsDiff diffA; setDiff(diffA, diffString, baseDiff, depth); VcsDiff diffB; diffB = diffA; // change a property of A diffA.setDiff(diffStringNew); compareDiff(diffA, diffStringNew, baseDiff, depth); compareDiff(diffB, diffString, baseDiff, depth); } } -QTEST_GUILESS_MAIN(TestVcsDiff); +QTEST_GUILESS_MAIN(TestVcsDiff) diff --git a/kdevplatform/vcs/tests/test_vcsevent.cpp b/kdevplatform/vcs/tests/test_vcsevent.cpp index 1c927a292c..888312875e 100644 --- a/kdevplatform/vcs/tests/test_vcsevent.cpp +++ b/kdevplatform/vcs/tests/test_vcsevent.cpp @@ -1,145 +1,145 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsevent.h" #include #include using namespace KDevelop; void TestVcsEvent::setEvent(VcsEvent& event, const VcsRevision& revision, const QString& author, const QDateTime& date, const QString& message, const QList& items) { event.setRevision(revision); event.setAuthor(author); event.setDate(date); event.setMessage(message); event.setItems(items); } void TestVcsEvent::compareEvent(const VcsEvent& event, const VcsRevision& revision, const QString& author, const QDateTime& date, const QString& message, const QList& items) { QCOMPARE(event.revision(), revision); QCOMPARE(event.author(), author); QCOMPARE(event.date(), date); QCOMPARE(event.message(), message); QCOMPARE(event.items().count(), items.count()); } void TestVcsEvent::testCopyConstructor() { // test plain copy VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QString author("author"); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString message("message"); const QList items({{}}); { VcsEvent eventA; setEvent(eventA, revision, author, date, message, items); VcsEvent eventB(eventA); compareEvent(eventA, revision, author, date, message, items); compareEvent(eventB, revision, author, date, message, items); } VcsRevision revisionNew; revisionNew.setRevisionValue(2, VcsRevision::FileNumber); // test detach after changing A { VcsEvent eventA; setEvent(eventA, revision, author, date, message, items); VcsEvent eventB(eventA); // change a property of A eventA.setRevision(revisionNew); compareEvent(eventA, revisionNew, author, date, message, items); compareEvent(eventB, revision, author, date, message, items); } } void TestVcsEvent::testAssignOperator() { // test plain copy VcsRevision revision; revision.setRevisionValue("A", VcsRevision::GlobalNumber); const QString author("author"); const QDateTime date = QDateTime::fromString("2001-01-01T00:00:00+00:00", Qt::ISODate); const QString message("message"); const QList items({{}}); { VcsEvent eventA; setEvent(eventA, revision, author, date, message, items); VcsEvent eventB; eventB = eventA; compareEvent(eventA, revision, author, date, message, items); compareEvent(eventB, revision, author, date, message, items); } VcsRevision revisionNew; revisionNew.setRevisionValue(2, VcsRevision::FileNumber); // test detach after changing A { VcsEvent eventA; setEvent(eventA, revision, author, date, message, items); VcsEvent eventB; eventB = eventA; // change a property of A eventA.setRevision(revisionNew); compareEvent(eventA, revisionNew, author, date, message, items); compareEvent(eventB, revision, author, date, message, items); } } -QTEST_GUILESS_MAIN(TestVcsEvent); +QTEST_GUILESS_MAIN(TestVcsEvent) diff --git a/kdevplatform/vcs/tests/test_vcsitemevent.cpp b/kdevplatform/vcs/tests/test_vcsitemevent.cpp index f0cee72696..64004ac6eb 100644 --- a/kdevplatform/vcs/tests/test_vcsitemevent.cpp +++ b/kdevplatform/vcs/tests/test_vcsitemevent.cpp @@ -1,145 +1,145 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsitemevent.h" #include #include using namespace KDevelop; void TestVcsItemEvent::setItemEvent(VcsItemEvent& itemEvent, const QString& repositoryLocation, const QString& repositoryCopySourceLocation, const VcsRevision& repositoryCopySourceRevision, VcsItemEvent::Actions actions) { itemEvent.setRepositoryLocation(repositoryLocation); itemEvent.setRepositoryCopySourceLocation(repositoryCopySourceLocation); itemEvent.setRepositoryCopySourceRevision(repositoryCopySourceRevision); itemEvent.setActions(actions); } void TestVcsItemEvent::compareItemEvent(const VcsItemEvent& itemEvent, const QString& repositoryLocation, const QString& repositoryCopySourceLocation, const VcsRevision& repositoryCopySourceRevision, VcsItemEvent::Actions actions) { QCOMPARE(itemEvent.repositoryLocation(), repositoryLocation); QCOMPARE(itemEvent.repositoryCopySourceLocation(), repositoryCopySourceLocation); QCOMPARE(itemEvent.repositoryCopySourceRevision(), repositoryCopySourceRevision); QCOMPARE(itemEvent.actions(), actions); } void TestVcsItemEvent::testCopyConstructor() { // test plain copy const QString repositoryLocation("location"); const QString repositoryCopySourceLocation("copy source location"); VcsRevision repositoryCopySourceRevision; repositoryCopySourceRevision.setRevisionValue("A", VcsRevision::GlobalNumber); const VcsItemEvent::Actions actions = VcsItemEvent::Added; { VcsItemEvent itemEventA; setItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); VcsItemEvent itemEventB(itemEventA); compareItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); compareItemEvent(itemEventB, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); } const QString repositoryLocationNew("new location"); // test detach after changing A { VcsItemEvent itemEventA; setItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); VcsItemEvent itemEventB(itemEventA); // change a property of A itemEventA.setRepositoryLocation(repositoryLocationNew); compareItemEvent(itemEventA, repositoryLocationNew, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); compareItemEvent(itemEventB, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); } } void TestVcsItemEvent::testAssignOperator() { // test plain assign const QString repositoryLocation("location"); const QString repositoryCopySourceLocation("copy source location"); VcsRevision repositoryCopySourceRevision; repositoryCopySourceRevision.setRevisionValue("A", VcsRevision::GlobalNumber); const VcsItemEvent::Actions actions = VcsItemEvent::Added; { VcsItemEvent itemEventA; setItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); VcsItemEvent itemEventB; itemEventB = itemEventA; compareItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); compareItemEvent(itemEventB, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); } const QString repositoryLocationNew("new location"); // test detach after changing A { VcsItemEvent itemEventA; setItemEvent(itemEventA, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); VcsItemEvent itemEventB; itemEventB = itemEventA; // change a property of A itemEventA.setRepositoryLocation(repositoryLocationNew); compareItemEvent(itemEventA, repositoryLocationNew, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); compareItemEvent(itemEventB, repositoryLocation, repositoryCopySourceLocation, repositoryCopySourceRevision, actions); } } -QTEST_GUILESS_MAIN(TestVcsItemEvent); +QTEST_GUILESS_MAIN(TestVcsItemEvent) diff --git a/kdevplatform/vcs/tests/test_vcslocation.cpp b/kdevplatform/vcs/tests/test_vcslocation.cpp index 0cc201728e..aaf8164057 100644 --- a/kdevplatform/vcs/tests/test_vcslocation.cpp +++ b/kdevplatform/vcs/tests/test_vcslocation.cpp @@ -1,222 +1,222 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcslocation.h" #include using namespace KDevelop; void TestVcsLocation::setServerLocation(VcsLocation& serverLocation, const QString& repositoryModule, const QString& repositoryBranch, const QString& repositoryTag, const QString& repositoryPath, const QVariant& userData) { serverLocation.setRepositoryModule(repositoryModule); serverLocation.setRepositoryBranch(repositoryBranch); serverLocation.setRepositoryTag(repositoryTag); serverLocation.setRepositoryPath(repositoryPath); serverLocation.setUserData(userData); } void TestVcsLocation::compareServerLocation(const VcsLocation& serverLocation, const QString& repositoryServer, const QString& repositoryModule, const QString& repositoryBranch, const QString& repositoryTag, const QString& repositoryPath, const QVariant& userData) { QCOMPARE(serverLocation.isValid(), true); QCOMPARE(serverLocation.type(), VcsLocation::RepositoryLocation); QCOMPARE(serverLocation.repositoryServer(), repositoryServer); QCOMPARE(serverLocation.repositoryModule(), repositoryModule); QCOMPARE(serverLocation.repositoryBranch(), repositoryBranch); QCOMPARE(serverLocation.repositoryTag(), repositoryTag); QCOMPARE(serverLocation.repositoryPath(), repositoryPath); QCOMPARE(serverLocation.userData(), userData); } void TestVcsLocation::testDefaultConstructor() { const VcsLocation location; QCOMPARE(location.isValid(), false); } void TestVcsLocation::testLocalUrlConstructor() { // valid { const QUrl localUrl = QUrl("file:///tmp/foo"); const VcsLocation localLocation(localUrl); QCOMPARE(localLocation.isValid(), true); QCOMPARE(localLocation.type(), VcsLocation::LocalLocation); QCOMPARE(localLocation.localUrl(), localUrl); } // invalid { const QUrl localUrl; const VcsLocation localLocation(localUrl); QCOMPARE(localLocation.isValid(), false); QCOMPARE(localLocation.type(), VcsLocation::LocalLocation); QCOMPARE(localLocation.localUrl(), localUrl); } } void TestVcsLocation::testRepositoryServerConstructor() { // valid { const QString repositoryServer = QStringLiteral("server"); const QString repositoryModule = QStringLiteral("module"); const QString repositoryBranch = QStringLiteral("branch"); const QString repositoryTag = QStringLiteral("tag"); const QString repositoryPath = QStringLiteral("path"); const QVariant userData = QVariant(QStringLiteral("userdata")); VcsLocation serverLocation(repositoryServer); setServerLocation(serverLocation, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); compareServerLocation(serverLocation, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); } // invalid { const QString repositoryServer; VcsLocation serverLocation(repositoryServer); QCOMPARE(serverLocation.isValid(), false); QCOMPARE(serverLocation.type(), VcsLocation::RepositoryLocation); QCOMPARE(serverLocation.repositoryServer(), repositoryServer); } } void TestVcsLocation::testCopyConstructor() { // test plain copy const QString repositoryServer = QStringLiteral("server"); const QString repositoryModule = QStringLiteral("module"); const QString repositoryBranch = QStringLiteral("branch"); const QString repositoryTag = QStringLiteral("tag"); const QString repositoryPath = QStringLiteral("path"); const QVariant userData = QVariant(QStringLiteral("userdata")); { VcsLocation serverLocationA(repositoryServer); setServerLocation(serverLocationA, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); VcsLocation serverLocationB(serverLocationA); compareServerLocation(serverLocationA, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); compareServerLocation(serverLocationB, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); QVERIFY(serverLocationB == serverLocationA); QVERIFY(serverLocationA == serverLocationB); } const QString repositoryServerNew = QStringLiteral("servernew"); // test detach after changing A { VcsLocation serverLocationA(repositoryServer); setServerLocation(serverLocationA, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); VcsLocation serverLocationB(serverLocationA); // change a property of A serverLocationA.setRepositoryServer(repositoryServerNew); compareServerLocation(serverLocationA, repositoryServerNew, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); compareServerLocation(serverLocationB, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); QVERIFY(!(serverLocationB == serverLocationA)); QVERIFY(!(serverLocationA == serverLocationB)); } } void TestVcsLocation::testAssignOperator() { // test plain copy const QString repositoryServer = QStringLiteral("server"); const QString repositoryModule = QStringLiteral("module"); const QString repositoryBranch = QStringLiteral("branch"); const QString repositoryTag = QStringLiteral("tag"); const QString repositoryPath = QStringLiteral("path"); const QVariant userData = QVariant(QStringLiteral("userdata")); { VcsLocation serverLocationA(repositoryServer); setServerLocation(serverLocationA, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); VcsLocation serverLocationB; serverLocationB = serverLocationA; compareServerLocation(serverLocationA, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); compareServerLocation(serverLocationB, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); QVERIFY(serverLocationB == serverLocationA); QVERIFY(serverLocationA == serverLocationB); } const QString repositoryServerNew = QStringLiteral("servernew"); // test detach after changing A { VcsLocation serverLocationA(repositoryServer); setServerLocation(serverLocationA, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); VcsLocation serverLocationB; serverLocationB = serverLocationA; // change a property of A serverLocationA.setRepositoryServer(repositoryServerNew); compareServerLocation(serverLocationA, repositoryServerNew, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); compareServerLocation(serverLocationB, repositoryServer, repositoryModule, repositoryBranch, repositoryTag, repositoryPath, userData); QVERIFY(!(serverLocationB == serverLocationA)); QVERIFY(!(serverLocationA == serverLocationB)); } } -QTEST_GUILESS_MAIN(TestVcsLocation); +QTEST_GUILESS_MAIN(TestVcsLocation) diff --git a/kdevplatform/vcs/tests/test_vcsrevision.cpp b/kdevplatform/vcs/tests/test_vcsrevision.cpp index 2c694d2d65..81fcad2a04 100644 --- a/kdevplatform/vcs/tests/test_vcsrevision.cpp +++ b/kdevplatform/vcs/tests/test_vcsrevision.cpp @@ -1,138 +1,138 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsrevision.h" #include #include using namespace KDevelop; void TestVcsRevision::testCopyConstructor() { // copy invalid { VcsRevision revisionA; VcsRevision revisionB(revisionA); QCOMPARE(revisionA.revisionType(), VcsRevision::Invalid); QCOMPARE(revisionB.revisionType(), VcsRevision::Invalid); QVERIFY(revisionB == revisionA); QVERIFY(revisionA == revisionB); } const VcsRevision::RevisionType revisionType = VcsRevision::GlobalNumber; const QString globalNumberValue("A"); // test plain copy { VcsRevision revisionA; revisionA.setRevisionValue(globalNumberValue, revisionType); VcsRevision revisionB(revisionA); QCOMPARE(revisionA.revisionType(), revisionType); QCOMPARE(revisionA.revisionValue().toString(), globalNumberValue); QCOMPARE(revisionB.revisionType(), revisionType); QCOMPARE(revisionB.revisionValue().toString(), globalNumberValue); QVERIFY(revisionB == revisionA); QVERIFY(revisionA == revisionB); } const VcsRevision::RevisionType revisionTypeNew = VcsRevision::FileNumber; const qlonglong fileNumberValueNew = 2; // test detach after changing A { VcsRevision revisionA; revisionA.setRevisionValue(globalNumberValue, revisionType); VcsRevision revisionB(revisionA); revisionA.setRevisionValue(fileNumberValueNew, revisionTypeNew); QCOMPARE(revisionA.revisionType(), revisionTypeNew); QCOMPARE(revisionA.revisionValue().toLongLong(), fileNumberValueNew); QCOMPARE(revisionB.revisionType(), revisionType); QCOMPARE(revisionB.revisionValue().toString(), globalNumberValue); QVERIFY(!(revisionB == revisionA)); QVERIFY(!(revisionA == revisionB)); } } void TestVcsRevision::testAssignOperator() { // assign invalid { const VcsRevision::RevisionType typeB = VcsRevision::FileNumber; const qlonglong fileNumberB = 2; VcsRevision revisionA; VcsRevision revisionB; revisionB.setRevisionValue(fileNumberB, typeB); revisionB = revisionA; QCOMPARE(revisionA.revisionType(), VcsRevision::Invalid); QCOMPARE(revisionB.revisionType(), VcsRevision::Invalid); QVERIFY(revisionB == revisionA); QVERIFY(revisionA == revisionB); } const VcsRevision::RevisionType revisionType = VcsRevision::GlobalNumber; const QString globalNumberValue("A"); // test plain assign { VcsRevision revisionA; revisionA.setRevisionValue(globalNumberValue, revisionType); VcsRevision revisionB; revisionB = revisionA; QCOMPARE(revisionA.revisionType(), revisionType); QCOMPARE(revisionA.revisionValue().toString(), globalNumberValue); QCOMPARE(revisionB.revisionType(), revisionType); QCOMPARE(revisionB.revisionValue().toString(), globalNumberValue); QVERIFY(revisionB == revisionA); QVERIFY(revisionA == revisionB); } const VcsRevision::RevisionType revisionTypeNew = VcsRevision::FileNumber; const qlonglong fileNumberValueNew = 2; // test detach after changing A { VcsRevision revisionA; revisionA.setRevisionValue(globalNumberValue, revisionType); VcsRevision revisionB; revisionB = revisionA; revisionA.setRevisionValue(fileNumberValueNew, revisionTypeNew); QCOMPARE(revisionA.revisionType(), revisionTypeNew); QCOMPARE(revisionA.revisionValue().toLongLong(), fileNumberValueNew); QCOMPARE(revisionB.revisionType(), revisionType); QCOMPARE(revisionB.revisionValue().toString(), globalNumberValue); QVERIFY(!(revisionB == revisionA)); QVERIFY(!(revisionA == revisionB)); } } -QTEST_GUILESS_MAIN(TestVcsRevision); +QTEST_GUILESS_MAIN(TestVcsRevision) diff --git a/kdevplatform/vcs/tests/test_vcsstatusinfo.cpp b/kdevplatform/vcs/tests/test_vcsstatusinfo.cpp index f00ebcea87..9a3d468fc9 100644 --- a/kdevplatform/vcs/tests/test_vcsstatusinfo.cpp +++ b/kdevplatform/vcs/tests/test_vcsstatusinfo.cpp @@ -1,113 +1,113 @@ /* This file is part of KDevelop * * Copyright 2017 Friedrich W. H. Kossebau * * 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 "test_vcsstatusinfo.h" #include using namespace KDevelop; void TestVcsStatusInfo::testCopyConstructor() { // test plain copy const QUrl url(QStringLiteral("git://foo")); const VcsStatusInfo::State state = VcsStatusInfo::ItemUpToDate; { VcsStatusInfo statusInfoA; statusInfoA.setUrl(url); statusInfoA.setState(state); VcsStatusInfo statusInfoB(statusInfoA); QCOMPARE(statusInfoA.url(), url); QCOMPARE(statusInfoA.state(), state); QCOMPARE(statusInfoB.url(), url); QCOMPARE(statusInfoB.state(), state); QVERIFY(statusInfoA == statusInfoB); QVERIFY(statusInfoB == statusInfoA); } const QUrl urlNew(QStringLiteral("svn://bar")); // test detach after changing A { VcsStatusInfo statusInfoA; statusInfoA.setUrl(url); statusInfoA.setState(state); VcsStatusInfo statusInfoB(statusInfoA); // change a property of A statusInfoA.setUrl(urlNew); QCOMPARE(statusInfoA.url(), urlNew); QCOMPARE(statusInfoA.state(), state); QCOMPARE(statusInfoB.url(), url); QCOMPARE(statusInfoB.state(), state); QVERIFY(statusInfoA != statusInfoB); QVERIFY(statusInfoB != statusInfoA); } } void TestVcsStatusInfo::testAssignOperator() { // test plain copy const QUrl url(QStringLiteral("git://foo")); const VcsStatusInfo::State state = VcsStatusInfo::ItemUpToDate; { VcsStatusInfo statusInfoA; statusInfoA.setUrl(url); statusInfoA.setState(state); VcsStatusInfo statusInfoB; statusInfoB = statusInfoA; QCOMPARE(statusInfoA.url(), url); QCOMPARE(statusInfoA.state(), state); QCOMPARE(statusInfoB.url(), url); QCOMPARE(statusInfoB.state(), state); QVERIFY(statusInfoA == statusInfoB); QVERIFY(statusInfoB == statusInfoA); } const QUrl urlNew(QStringLiteral("svn://bar")); // test detach after changing A { VcsStatusInfo statusInfoA; statusInfoA.setUrl(url); statusInfoA.setState(state); VcsStatusInfo statusInfoB; statusInfoB = statusInfoA; // change a property of A statusInfoA.setUrl(urlNew); QCOMPARE(statusInfoA.url(), urlNew); QCOMPARE(statusInfoA.state(), state); QCOMPARE(statusInfoB.url(), url); QCOMPARE(statusInfoB.state(), state); QVERIFY(statusInfoA != statusInfoB); QVERIFY(statusInfoB != statusInfoA); } } -QTEST_GUILESS_MAIN(TestVcsStatusInfo); +QTEST_GUILESS_MAIN(TestVcsStatusInfo) diff --git a/plugins/clang/clangsettings/clangsettingsmanager.h b/plugins/clang/clangsettings/clangsettingsmanager.h index 2a0b1d5388..fbfbf2b22c 100644 --- a/plugins/clang/clangsettings/clangsettingsmanager.h +++ b/plugins/clang/clangsettings/clangsettingsmanager.h @@ -1,78 +1,78 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CLANGSETTINGSMANAGER_H #define CLANGSETTINGSMANAGER_H #include #include #include "clangprivateexport.h" namespace KDevelop { class ProjectBaseItem; class IProject; } struct ParserSettings { QString parserOptions; bool isCpp() const; QVector toClangAPI() const; bool operator==(const ParserSettings& rhs) const; }; -Q_DECLARE_METATYPE(ParserSettings); +Q_DECLARE_METATYPE(ParserSettings) struct CodeCompletionSettings { bool macros = true; bool lookAhead = false; }; struct AssistantsSettings { bool forwardDeclare = true; }; class KDEVCLANGPRIVATE_EXPORT ClangSettingsManager { public: static ClangSettingsManager* self(); AssistantsSettings assistantsSettings() const; CodeCompletionSettings codeCompletionSettings() const; ParserSettings parserSettings(KDevelop::ProjectBaseItem* item) const; ParserSettings parserSettings(const QString& path) const; private: ClangSettingsManager(); bool m_enableTesting = false; friend class CodeCompletionTestBase; }; #endif // CLANGSETTINGSMANAGER_H diff --git a/plugins/clang/codegen/codegenhelper.h b/plugins/clang/codegen/codegenhelper.h index 910f912368..270332e14e 100644 --- a/plugins/clang/codegen/codegenhelper.h +++ b/plugins/clang/codegen/codegenhelper.h @@ -1,48 +1,48 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CODEGENHELPER_H #define CODEGENHELPER_H #include #include #include struct Signature; namespace CodegenHelper { ///Returns the type that should be used for shortened printing of the same. KDevelop::AbstractType::Ptr typeForShortenedString(KDevelop::Declaration* decl); ///Returns a shortened string version of the type attached to the given declaration. ///@param desiredLength the desired length. No guarantee that the resulting string will be this short. With the default-value, no shortening will happen in most cases. ///@param ctx visibility context to consider. All prefixes of types are shortened to the minimum length while staying visible from here ///@param stripPrefix this prefix will be stripped from qualified identifiers. This is useful to remove parts of the current context. QString shortenedTypeString(KDevelop::Declaration* decl, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); QString shortenedTypeString(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); KDevelop::IndexedTypeIdentifier shortenedTypeIdentifier(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); ///Returns a simplified string version of the given type: Template default-parameters are stripped away, qualified identifiers are simplified so they are as short as possible, while staying visible from the given context. QString simplifiedTypeString(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* visibilityFrom); QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition); -}; +} #endif // CODEGENHELPER_H diff --git a/plugins/clang/duchain/clangpch.h b/plugins/clang/duchain/clangpch.h index 1d8cee841e..40ebffed38 100644 --- a/plugins/clang/duchain/clangpch.h +++ b/plugins/clang/duchain/clangpch.h @@ -1,51 +1,51 @@ /* * Copyright 2014 Olivier de Gaalon * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CLANGPCH_H #define CLANGPCH_H #include #include #include "parsesession.h" #include "clanghelpers.h" class ClangParsingEnvironment; class KDEVCLANGPRIVATE_EXPORT ClangPCH { public: ClangPCH(const ClangParsingEnvironment& environment, ClangIndex* index); IncludeFileContexts mapIncludes(CXTranslationUnit tu) const; CXFile mapFile(CXTranslationUnit tu) const; KDevelop::ReferencedTopDUContext context() const; private: - Q_DISABLE_COPY(ClangPCH); + Q_DISABLE_COPY(ClangPCH) IncludeFileContexts m_includes; KDevelop::ReferencedTopDUContext m_context; ParseSession m_session; }; #endif //CLANGPCH_H diff --git a/plugins/clang/duchain/documentfinderhelpers.h b/plugins/clang/duchain/documentfinderhelpers.h index 996823b255..fdcb5c2fe1 100644 --- a/plugins/clang/duchain/documentfinderhelpers.h +++ b/plugins/clang/duchain/documentfinderhelpers.h @@ -1,61 +1,61 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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. * */ #ifndef DOCUMENTFINDERHELPERS_H #define DOCUMENTFINDERHELPERS_H #include #include #include "clangprivateexport.h" class QStringList; class QString; /// Helper class for handling @see IBuddyDocumentFinder features. namespace DocumentFinderHelpers { /// @return All supported mime types KDEVCLANGPRIVATE_EXPORT QStringList mimeTypesList(); /** * Considers the URLs as buddy documents if the base path (without extension) * is the same, and one extension starts with h/H and the other one with c/C. * For example, foo.hpp and foo.C are buddies. */ KDEVCLANGPRIVATE_EXPORT bool areBuddies(const QUrl &url1, const QUrl& url2); /// @see KDevelop::IBuddyDocumentFinder KDEVCLANGPRIVATE_EXPORT bool buddyOrder(const QUrl &url1, const QUrl& url2); /// @see KDevelop::IBuddyDocumentFinder KDEVCLANGPRIVATE_EXPORT QVector< QUrl > getPotentialBuddies(const QUrl &url, bool checkDUChain = true); /** * Returns path to the source file for given @p headerPath * * If no source file exists or @p headerPath is not a header an empty sting is returned */ KDEVCLANGPRIVATE_EXPORT QString sourceForHeader(const QString& headerPath); -}; +} #endif // DOCUMENTFINDERHELPERS_H diff --git a/plugins/clang/duchain/duchainutils.h b/plugins/clang/duchain/duchainutils.h index 79b25469e2..66119ced49 100644 --- a/plugins/clang/duchain/duchainutils.h +++ b/plugins/clang/duchain/duchainutils.h @@ -1,57 +1,57 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef DUCHAINUTILS_H #define DUCHAINUTILS_H #include "clangprivateexport.h" #include "duchain/parsesession.h" namespace KTextEditor { class Range; } namespace KDevelop { class Declaration; } namespace ClangIntegration { namespace DUChainUtils { KDEVCLANGPRIVATE_EXPORT KTextEditor::Range functionSignatureRange(const KDevelop::Declaration* decl); KDEVCLANGPRIVATE_EXPORT void registerDUChainItems(); KDEVCLANGPRIVATE_EXPORT void unregisterDUChainItems(); /** * Finds attached parse session data (aka AST) to the @p file * * If no session data found, then @p tuFile asked for the attached session data */ KDEVCLANGPRIVATE_EXPORT ParseSessionData::Ptr findParseSessionData(const KDevelop::IndexedString &file, const KDevelop::IndexedString &tufile); KDEVCLANGPRIVATE_EXPORT QString clangBuiltinIncludePath(); -}; +} } #endif // DUCHAINUTILS_H diff --git a/plugins/clang/duchain/parsesession.h b/plugins/clang/duchain/parsesession.h index ab7f8a5727..8129053ade 100644 --- a/plugins/clang/duchain/parsesession.h +++ b/plugins/clang/duchain/parsesession.h @@ -1,140 +1,140 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PARSESESSION_H #define PARSESESSION_H #include #include #include #include #include #include #include #include "clangprivateexport.h" #include "clangparsingenvironment.h" #include "unsavedfile.h" class ClangIndex; class KDEVCLANGPRIVATE_EXPORT ParseSessionData : public KDevelop::IAstContainer { public: using Ptr = QExplicitlySharedDataPointer; enum Option { NoOption, ///< No special options SkipFunctionBodies, ///< Pass CXTranslationUnit_SkipFunctionBodies (likely unwanted) PrecompiledHeader ///< Pass CXTranslationUnit_PrecompiledPreamble and others to cache precompiled headers }; Q_DECLARE_FLAGS(Options, Option) /** * Parse the given @p contents. * * @param unsavedFiles Optional unsaved document contents from the editor. */ ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options = Options()); ~ParseSessionData() override; ClangParsingEnvironment environment() const; private: friend class ParseSession; void setUnit(CXTranslationUnit unit); QByteArray writeDefinesFile(const QMap& defines); QMutex m_mutex; CXFile m_file = nullptr; CXTranslationUnit m_unit = nullptr; ClangParsingEnvironment m_environment; /// TODO: share this file for all TUs that use the same defines (probably most in a project) /// best would be a PCH, if possible QTemporaryFile m_definesFile; // cached ProblemPointer representation for diagnostics QVector m_diagnosticsCache; }; /** * Thread-safe utility class around a CXTranslationUnit. * * It will lock the mutex of the currently set ParseSessionData and thereby ensure * only one ParseSession can operate on a given CXTranslationUnit stored therein. */ class KDEVCLANGPRIVATE_EXPORT ParseSession { public: /** * @return a unique identifier for Clang documents. */ static KDevelop::IndexedString languageString(); /** * Initialize a parse session with the given data and, if that data is valid, lock its mutex. */ explicit ParseSession(const ParseSessionData::Ptr& data); /** * Unlocks the mutex of the currently set ParseSessionData. */ ~ParseSession(); /** * Unlocks the mutex of the currently set ParseSessionData, and instead acquire the lock in @p data. */ void setData(const ParseSessionData::Ptr& data); ParseSessionData::Ptr data() const; /** * @return find the CXFile for the given path. */ CXFile file(const QByteArray& path) const; /** * @return the CXFile for the first file in this translation unit. */ CXFile mainFile() const; QList problemsForFile(CXFile file) const; CXTranslationUnit unit() const; bool reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment); ClangParsingEnvironment environment() const; private: - Q_DISABLE_COPY(ParseSession); + Q_DISABLE_COPY(ParseSession) ParseSessionData::Ptr d; }; #endif // PARSESESSION_H diff --git a/plugins/clang/tests/bench_codecompletion.cpp b/plugins/clang/tests/bench_codecompletion.cpp index ec8a591da9..f25f6f191f 100644 --- a/plugins/clang/tests/bench_codecompletion.cpp +++ b/plugins/clang/tests/bench_codecompletion.cpp @@ -1,97 +1,97 @@ /* * Copyright 2016 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "bench_codecompletion.h" #include #include #include #include #include #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "codecompletion/model.h" -QTEST_MAIN(BenchCodeCompletion); +QTEST_MAIN(BenchCodeCompletion) using namespace KDevelop; BenchCodeCompletion::BenchCodeCompletion() : m_index(new ClangIndex) , m_model(new ClangCodeCompletionModel(m_index.data(), this)) { m_model->initialize(); } BenchCodeCompletion::~BenchCodeCompletion() = default; void BenchCodeCompletion::benchCodeCompletion_data() { QTest::addColumn("code"); QTest::addColumn("position"); QTest::newRow("empty") << "" << KTextEditor::Cursor(0, 0); QTest::newRow("stl") << R"( #include #include #include int main() { return 0; } )" << KTextEditor::Cursor(7, 0); QTest::newRow("clib") << R"( #include #include #include int main() { return 0; } )" << KTextEditor::Cursor(7, 0); } void BenchCodeCompletion::benchCodeCompletion() { QFETCH(QString, code); QFETCH(KTextEditor::Cursor, position); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST, 1, 5000)); auto view = createView(file.url().toUrl(), this); QSignalSpy spy(m_model, &QAbstractItemModel::modelReset); QBENCHMARK { m_model->completionInvoked(view.get(), {position, position}, KTextEditor::CodeCompletionModel::UserInvocation); do { spy.wait(); } while (!m_model->rowCount()); } } diff --git a/plugins/clang/tests/bench_duchain.cpp b/plugins/clang/tests/bench_duchain.cpp index f87b9b02d0..66e204a5ac 100644 --- a/plugins/clang/tests/bench_duchain.cpp +++ b/plugins/clang/tests/bench_duchain.cpp @@ -1,79 +1,79 @@ /* * Copyright 2016 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "bench_duchain.h" #include #include #include #include #include #include #include using namespace KDevelop; BenchDUChain::BenchDUChain() { } BenchDUChain::~BenchDUChain() { } void BenchDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); TestCore::initialize(Core::NoUi); } void BenchDUChain::cleanupTestCase() { TestCore::shutdown(); } void BenchDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } -QTEST_MAIN(BenchDUChain); +QTEST_MAIN(BenchDUChain) diff --git a/plugins/clang/tests/codecompletiontestbase.h b/plugins/clang/tests/codecompletiontestbase.h index 7bd6251a41..6d0bcc6cff 100644 --- a/plugins/clang/tests/codecompletiontestbase.h +++ b/plugins/clang/tests/codecompletiontestbase.h @@ -1,60 +1,60 @@ /* * Copyright 2014 David Stevens * Copyright 2014 Kevin Funk * Copyright 2016 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CODECOMPLETIONTESTBASE_H #define CODECOMPLETIONTESTBASE_H #include #include namespace KTextEditor { class View; } namespace KDevelop { class TestProjectController; -}; +} struct DeleteDocument { void operator()(KTextEditor::View* view) const; }; class CodeCompletionTestBase : public QObject { Q_OBJECT public: std::unique_ptr createView(const QUrl& url, QObject* parent) const; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); protected: KDevelop::TestProjectController* m_projectController; }; #endif // CODECOMPLETIONTESTBASE_H diff --git a/plugins/clang/tests/test_codecompletion.cpp b/plugins/clang/tests/test_codecompletion.cpp index ac9ca6377a..94e05da8bf 100644 --- a/plugins/clang/tests/test_codecompletion.cpp +++ b/plugins/clang/tests/test_codecompletion.cpp @@ -1,1411 +1,1411 @@ /* * Copyright 2014 David Stevens * Copyright 2014 Kevin Funk * Copyright 2015 Milian Wolff * Copyright 2015 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_codecompletion.h" #include #include #include #include #include #include "duchain/parsesession.h" #include "util/clangtypes.h" #include #include #include #include #include "codecompletion/completionhelper.h" #include "codecompletion/context.h" #include "codecompletion/includepathcompletioncontext.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include -QTEST_MAIN(TestCodeCompletion); +QTEST_MAIN(TestCodeCompletion) static const auto NoMacroOrBuiltin = ClangCodeCompletionContext::ContextFilters( ClangCodeCompletionContext::NoBuiltins | ClangCodeCompletionContext::NoMacros); using namespace KDevelop; using ClangCodeCompletionItemTester = CodeCompletionItemTester; struct CompletionItems { CompletionItems(){} CompletionItems(const KTextEditor::Cursor& position, const QStringList& completions, const QStringList& declarationItems = {}) : position(position) , completions(completions) , declarationItems(declarationItems) {}; KTextEditor::Cursor position; QStringList completions; QStringList declarationItems; ///< completion items that have associated declarations. Declarations with higher match quality at the top. @sa KTextEditor::CodeCompletionModel::MatchQuality }; Q_DECLARE_TYPEINFO(CompletionItems, Q_MOVABLE_TYPE); -Q_DECLARE_METATYPE(CompletionItems); +Q_DECLARE_METATYPE(CompletionItems) struct CompletionPriorityItem { CompletionPriorityItem(const QString& name, int matchQuality = 0, int inheritanceDepth = 0, const QString& failMessage = {}) : name(name) , failMessage(failMessage) , matchQuality(matchQuality) , inheritanceDepth(inheritanceDepth) {} QString name; QString failMessage; int matchQuality; int inheritanceDepth; }; struct CompletionPriorityItems : public CompletionItems { CompletionPriorityItems(){} CompletionPriorityItems(const KTextEditor::Cursor& position, const QList& completions) : CompletionItems(position, {}) , completions(completions) {}; QList completions; }; Q_DECLARE_TYPEINFO(CompletionPriorityItems, Q_MOVABLE_TYPE); -Q_DECLARE_METATYPE(CompletionPriorityItems); +Q_DECLARE_METATYPE(CompletionPriorityItems) namespace { struct NoopTestFunction { void operator()(const ClangCodeCompletionItemTester& /*tester*/) const { } }; QString textForDocument(const QUrl& url, const KTextEditor::Cursor& position) { bool close = false; auto* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc) { doc = ICore::self()->documentController()->openDocument(url); close = true; } auto text = doc->textDocument()->text({{0, 0}, position}); if (close) { doc->close(IDocument::Discard); } return text; } QExplicitlySharedDataPointer createContext(const ReferencedTopDUContext& top, const ParseSessionData::Ptr& sessionData, const KTextEditor::Cursor position, const QString& code = {}) { const auto url = top->url().toUrl(); const auto text = code.isEmpty() ? textForDocument(url, position) : code; return QExplicitlySharedDataPointer{ new ClangCodeCompletionContext(DUContextPointer(top), sessionData, url, position, text)}; } template void executeCompletionTest(const ReferencedTopDUContext& top, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { DUChainReadLocker lock; const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); // TODO: We should not need to pass 'session' to the context, should just use the base class ctor auto context = createContext(top, sessionData, expectedCompletionItems.position); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(context); int previousMatchQuality = 10; for(const auto& declarationName : expectedCompletionItems.declarationItems){ const auto declarationItem = tester.findItem(declarationName); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); QVERIFY(matchQuality <= previousMatchQuality); previousMatchQuality = matchQuality; } tester.names.sort(); QEXPECT_FAIL("look-ahead function primary type argument", "No API in LibClang to determine expected code completion type", Continue); QEXPECT_FAIL("look-ahead template parameter substitution", "No parameters substitution so far", Continue); #if CINDEX_VERSION_MINOR < 30 QEXPECT_FAIL("look-ahead auto item", "Auto type, like many other types, is not exposed through LibClang. We assign DelayedType to it instead of IdentifiedType", Continue); #endif if (QTest::currentTestFunction() == QByteArrayLiteral("testImplementAfterEdit") && expectedCompletionItems.position.line() == 3) { QEXPECT_FAIL("", "TU is not properly updated after edit", Continue); } if (tester.names.size() != expectedCompletionItems.completions.size()) { qDebug() << "different results:\nactual:" << tester.names << "\nexpected:" << expectedCompletionItems.completions; } QCOMPARE(tester.names, expectedCompletionItems.completions); customTestFunction(tester); } template void executeCompletionTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), expectedCompletionItems, filters, customTestFunction); } void executeCompletionPriorityTest(const QString& code, const CompletionPriorityItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); // don't hold DUChain lock when constructing ClangCodeCompletionContext lock.unlock(); auto context = createContext(top, sessionData, expectedCompletionItems.position); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(context); for(const auto& declaration : expectedCompletionItems.completions){ const auto declarationItem = tester.findItem(declaration.name); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); auto inheritanceDepth = declarationItem->inheritanceDepth(); if(!declaration.failMessage.isEmpty()){ QEXPECT_FAIL("", declaration.failMessage.toUtf8().constData(), Continue); } QVERIFY(matchQuality == declaration.matchQuality && inheritanceDepth == declaration.inheritanceDepth); } } void executeMemberAccessReplacerTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, QStringLiteral("cpp")); auto document = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); auto view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); view->setCursorPosition(expectedCompletionItems.position); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); auto context = createContext(top, sessionData, expectedCompletionItems.position, code); QApplication::processEvents(); document->close(KDevelop::IDocument::Silent); // The previous ClangCodeCompletionContext call should replace member access. // That triggers an update request in the duchain which we are not interested in, // so let's stop that request. ICore::self()->languageController()->backgroundParser()->removeDocument(file.url()); context = createContext(top, sessionData, expectedCompletionItems.position); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(context); tester.names.sort(); QCOMPARE(tester.names, expectedCompletionItems.completions); } using IncludeTester = CodeCompletionItemTester; QExplicitlySharedDataPointer executeIncludePathCompletion(TestFile* file, const KTextEditor::Cursor& position) { if (!file->parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } DUChainReadLocker lock; auto top = file->topContext(); if (!top) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); if (!sessionData) { QTest::qFail("Failed to acquire parse session data.", __FILE__, __LINE__); return {}; } DUContextPointer topPtr(top); lock.unlock(); auto text = file->fileContents(); int textLength = -1; if (position.isValid()) { textLength = 0; for (int i = 0; i < position.line(); ++i) { textLength = text.indexOf('\n', textLength) + 1; } textLength += position.column(); } auto context = new IncludePathCompletionContext(topPtr, sessionData, file->url().toUrl(), position, text.mid(0, textLength)); return QExplicitlySharedDataPointer{context}; } } void TestCodeCompletion::testClangCodeCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testClangCodeCompletion_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("assignment") << "int foo = 5; \nint bar = " << CompletionItems{{1,9}, { "bar", "foo", }, {"bar","foo"}}; QTest::newRow("dotmemberaccess") << "class Foo { public: void foo() {} bool operator=(Foo &&) }; int main() { Foo f; \nf. " << CompletionItems{{1, 2}, { "foo", "operator=" }, {"foo", "operator="}}; QTest::newRow("arrowmemberaccess") << "class Foo { public: void foo() {} }; int main() { Foo* f = new Foo; \nf-> }" << CompletionItems{{1, 3}, { "foo" }, {"foo"}}; QTest::newRow("enum-case") << "enum Foo { foo, bar }; int main() { Foo f; switch (f) {\ncase " << CompletionItems{{1,4}, { "bar", "foo", }, {"foo", "bar"}}; QTest::newRow("only-private") << "class SomeStruct { private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { }}; QTest::newRow("private-friend") << "class SomeStruct { private: void priv() {} friend int main(); };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "priv", }, {"priv"}}; QTest::newRow("private-public") << "class SomeStruct { public: void pub() {} private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("protected-public") << "class SomeStruct { public: void pub() {} protected: void prot() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("localVariable") << "int main() { int localVariable;\nloc " << CompletionItems{{1, 3}, {"localVariable","main"}, {"localVariable", "main"} }; QTest::newRow("globalVariable") << "int globalVariable;\nint main() { \ngl " << CompletionItems{{2, 2}, {"globalVariable","main"}, {"globalVariable", "main"} }; QTest::newRow("namespaceVariable") << "namespace NameSpace{int variable};\nint main() { \nNameSpace:: " << CompletionItems{{2, 11}, {"variable"}, {"variable"} }; QTest::newRow("parentVariable") << "class A{public: int m_variable;};class B : public A{};\nint main() { B b;\nb. " << CompletionItems{{2, 2}, {"m_variable"}, {"m_variable"} }; QTest::newRow("itemsPriority") << "class A; class B; void f(A); int main(){ A c; B b;f(\n} " << CompletionItems{{1, 0}, {"A", "B", "b", "c", "f", #if CINDEX_VERSION_MINOR >= 30 "f", #endif "main"}, {"c", "A", "b", "B"} }; QTest::newRow("function-arguments") << "class Abc; int f(Abc){\n " << CompletionItems{{1, 0}, { "Abc", "f", }}; QTest::newRow("look-ahead int") << "struct LookAhead { int intItem;}; int main() {LookAhead* pInstance; LookAhead instance; int i =\n }" << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead class") << "class Class{}; struct LookAhead {Class classItem;}; int main() {LookAhead* pInstance; LookAhead instance; Class cl =\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "cl", "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function argument") << "class Class{}; struct LookAhead {Class classItem;}; void function(Class cl);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "function", #if CINDEX_VERSION_MINOR >= 30 "function", #endif "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function primary type argument") << "struct LookAhead {double doubleItem;}; void function(double double);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "LookAhead", "function", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead typedef") << "typedef double DOUBLE; struct LookAhead {DOUBLE doubleItem;};" "int main() {LookAhead* pInstance; LookAhead instance; double i =\n " << CompletionItems{{1, 0}, { "DOUBLE", "LookAhead", "i", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead pointer") << "struct LookAhead {int* pInt;};" "int main() {LookAhead* pInstance; LookAhead instance; int* i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.pInt", "main", "pInstance", "pInstance->pInt", }}; QTest::newRow("look-ahead template") << "template struct LookAhead {int intItem;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead template parameter substitution") << "template struct LookAhead {T itemT;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.itemT", "main", "pInstance", "pInstance->itemT", }}; QTest::newRow("look-ahead item access") << "class Class { public: int publicInt; protected: int protectedInt; private: int privateInt;};" "int main() {Class cl; int i =\n " << CompletionItems{{1, 0}, { "Class", "cl", "cl.publicInt", "i", "main", }}; QTest::newRow("look-ahead auto item") << "struct LookAhead { int intItem; };" "int main() {auto instance = LookAhead(); int i = \n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main" }}; QTest::newRow("variadic template recursive class") << R"( template struct my_class : Head, my_class { using base = Head; };)" << CompletionItems{{3, 17}, { "Head", "Tail", "my_class" }}; } void TestCodeCompletion::testReplaceMemberAccess() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeMemberAccessReplacerTest(code, expectedItems); } void TestCodeCompletion::testReplaceMemberAccess_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("replace arrow to dot") << "struct Struct { void function(); };" "int main() { Struct s; \ns-> " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("replace dot to arrow") << "struct Struct { void function(); };" "int main() { Struct* s; \ns. " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("no replacement needed") << "int main() { double a = \n0. " << CompletionItems{{1, 2}, { }}; } void TestCodeCompletion::testVirtualOverride() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testVirtualOverride_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "class Foo { virtual void foo(); virtual void foo(char c); virtual char foo(char c, int i, double d); };\n" "class Bar : Foo \n{void foo(char c) override;\n}" << CompletionItems{{3, 1}, {"foo()", "foo(char c, int i, double d)"}}; QTest::newRow("template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a); } ;\n" "class Bar : Foo \n{double overridden(char a) override;\n}" << CompletionItems{{3, 1}, {"foo(char a, double b, int i)"}}; QTest::newRow("nested-template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a, T2 b, int i); } ;\n" "template class Baz { };\n" "class Bar : Foo> \n{Baz overridden(char a, Baz b, int i) override;\n}" << CompletionItems{{4, 1}, {"foo(char a, Baz b, int i)"}}; QTest::newRow("multi") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz { virtual char baz(char c); };\n" "class Bar : Foo, Baz \n{int overridden(int i) override;\n}" << CompletionItems{{4, 1}, {"baz(char c)", "foo(int i)"}}; QTest::newRow("deep") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz : Foo { };\n" "class Bar : Baz \n{int overridden(int i) override;\n}" << CompletionItems{{4, 1}, {"foo(int i)"}}; QTest::newRow("repeated") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz : Foo { int foo(int i) override; };\n" "class Bar : Baz \n{int overridden(int i) override;\n}" << CompletionItems{{4, 1}, {"foo(int i)"}}; QTest::newRow("pure") << "class Foo { virtual void foo() = 0; virtual void overridden() = 0;};\n" "class Bar : Foo \n{void overridden() override;\n};" << CompletionItems{{3, 0}, {"foo() = 0"}}; QTest::newRow("repeated-pure") << "class Foo { virtual void foo() = 0; virtual void overridden() = 0; };\n" "class Baz : Foo { void foo() override; };\n" "class Bar : Baz \n{void overridden() override;\n}" << CompletionItems{{4, 1}, {"foo()"}}; QTest::newRow("const") << "class Foo { virtual void foo(const int b) const; virtual void overridden(const int b) const; }\n;" "class Bar : Foo \n{void overridden(const int b) const override;\n}" << CompletionItems{{3, 1}, {"foo(const int b) const"}}; QTest::newRow("dont-override") << R"(class A { virtual void something() = 0; }; class B : public A { public: void foo(); }; void B::foo() {} )" << CompletionItems{{8, 14}, {}}; } void TestCodeCompletion::testImplement() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testImplement_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "int foo(char c, int i); \n" << CompletionItems{{1, 0}, {"foo(char c, int i)"}}; QTest::newRow("class") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {}}; QTest::newRow("class2") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("namespace") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{3, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace2") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{4, 0}, {}}; QTest::newRow("namespace2") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("two-namespace") << "namespace Foo { int bar(char c, int i); };\n" "namespace Foo {\n" "};\n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("destructor") << "class Foo { ~Foo(); }\n" << CompletionItems{{1, 0}, {"Foo::~Foo()"}}; QTest::newRow("constructor") << "class Foo { \n" "Foo(int i); \n" "}; \n" << CompletionItems{{3, 1}, {"Foo::Foo(int i)"}}; QTest::newRow("template") << "template class Foo { T bar(T t); };\n" << CompletionItems{{1, 1}, {"Foo::bar(T t)"}}; QTest::newRow("specialized-template") << "template class Foo { \n" "T bar(T t); \n" "}; \n" "template T Foo::bar(T t){} \n" "template<> class Foo { \n" "int bar(int t); \n" "}\n" << CompletionItems{{6, 1}, {"Foo::bar(int t)"}}; QTest::newRow("nested-class") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {}}; QTest::newRow("nested-class2") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {}}; QTest::newRow("nested-class3") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {"baz(char c, int i)"}}; QTest::newRow("nested-namespace2") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {"Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace3") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("partial-template") << "template class Foo { \n" "template class Bar;\n" "template class Bar { void baz(T t, U u); }\n" "}\n" << CompletionItems{{5,1}, {"Foo::Bar::baz(T t, U u)"}}; QTest::newRow("variadic") << "int foo(...); int bar(int i, ...); \n" << CompletionItems{{1, 1}, {"bar(int i, ...)", "foo(...)"}}; QTest::newRow("const") << "class Foo { int bar() const; };" << CompletionItems{{3, 1}, {"Foo::bar() const"}}; QTest::newRow("multiple-methods") << "class Foo { int bar(); void foo(); char asdf() const; };" << CompletionItems{{1, 1}, {"Foo::asdf() const", "Foo::bar()", "Foo::foo()"}}; // explicitly deleted/defaulted functions should not appear in the implements completion QTest::newRow("deleted-copy-ctor") << "struct S { S(); S(const S&) = /*some comment*/ delete; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("deleted-overload-member") << "struct Foo {\n" " int x();\n" " int x(int) =delete;\n" "};\n" << CompletionItems{{5,1}, {"Foo::x()"}}; QTest::newRow("deleted-overload-global") << "int x();\n" "int x(int)= delete;\n" << CompletionItems{{2,1}, {"x()"}}; QTest::newRow("defaulted-copy-ctor") << "struct S { S(); S(const S&) = default; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("defaulted-dtor") << "struct Foo {\n" " Foo();\n" " ~Foo() =default;\n" "};\n" << CompletionItems{{5,1}, {"Foo::Foo()"}}; QTest::newRow("bug355163") << R"( #include namespace test { template struct IsSafeConversion : public std::is_same::type> { }; } // namespace test )" << CompletionItems{{7,0}, {}}; QTest::newRow("bug355954") << R"( struct Hello { struct Private; }; struct Hello::Private { void test(); }; )" << CompletionItems{{8,0}, {"Hello::Private::test()"}}; QTest::newRow("lineOfNextFunction") << "void foo();\nvoid bar() {}" << CompletionItems{{1,0}, {"foo()"}}; QTest::newRow("pure") << R"( struct Hello { virtual void foo() = 0; virtual void bar(); }; )" << CompletionItems{{5, 0}, {"Hello::bar()"}}; QTest::newRow("bug368544") << R"( class Klass { public: template void func(int a, T x, int b) const; }; )" << CompletionItems{{6, 0}, {"Klass::func(int a, T x, int b) const"}}; } void TestCodeCompletion::testImplementOtherFile() { TestFile header1(QStringLiteral("void foo();"), QStringLiteral("h")); QVERIFY(header1.parseAndWait()); TestFile header2(QStringLiteral("void bar();"), QStringLiteral("h")); QVERIFY(header2.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "#include \"%2\"\n" "void asdf();\n\n") .arg(header1.url().str()) .arg(header2.url().str()), QStringLiteral("cpp"), &header1); CompletionItems expectedItems{{3,1}, {"asdf()", "foo()"}}; QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(impl.topContext(), expectedItems); } void TestCodeCompletion::testImplementAfterEdit() { TestFile header1(QStringLiteral("void foo();"), QStringLiteral("h")); QVERIFY(header1.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "void asdf() {}\nvoid bar() {}") .arg(header1.url().str()), QStringLiteral("cpp"), &header1); auto document = ICore::self()->documentController()->openDocument(impl.url().toUrl()); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); CompletionItems expectedItems{{2,0}, {"foo()"}}; executeCompletionTest(impl.topContext(), expectedItems); document->textDocument()->insertText(expectedItems.position, QStringLiteral("\n")); expectedItems.position.setLine(3); executeCompletionTest(impl.topContext(), expectedItems); document->close(IDocument::Discard); } void TestCodeCompletion::testInvalidCompletions() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testInvalidCompletions_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("invalid-context-incomment") << "class Foo { int bar() const; };\n/*\n*/" << CompletionItems{{2, 0}, {}}; } void TestCodeCompletion::testIncludePathCompletion_data() { QTest::addColumn("code"); QTest::addColumn("cursor"); QTest::addColumn("itemId"); QTest::addColumn("result"); QTest::newRow("global-1") << QStringLiteral("#include ") << KTextEditor::Cursor(0, 9) << QStringLiteral("iostream") << QStringLiteral("#include "); QTest::newRow("global-2") << QStringLiteral("#include <") << KTextEditor::Cursor(0, 9) << QStringLiteral("iostream") << QStringLiteral("#include "); QTest::newRow("global-3") << QStringLiteral("#include <") << KTextEditor::Cursor(0, 10) << QStringLiteral("iostream") << QStringLiteral("#include "); QTest::newRow("global-4") << QStringLiteral("# include <") << KTextEditor::Cursor(0, 12) << QStringLiteral("iostream") << QStringLiteral("# include "); QTest::newRow("global-5") << QStringLiteral("# include <") << KTextEditor::Cursor(0, 14) << QStringLiteral("iostream") << QStringLiteral("# include "); QTest::newRow("global-6") << QStringLiteral("# include <> /* 1 */") << KTextEditor::Cursor(0, 14) << QStringLiteral("iostream") << QStringLiteral("# include /* 1 */"); QTest::newRow("global-7") << QStringLiteral("# include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 21) << QStringLiteral("iostream") << QStringLiteral("# include /* 1 */ /* 1 */"); QTest::newRow("global-8") << QStringLiteral("# /* 1 */ include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 28) << QStringLiteral("iostream") << QStringLiteral("# /* 1 */ include /* 1 */ /* 1 */"); QTest::newRow("global-9") << QStringLiteral("#include ") << KTextEditor::Cursor(0, 10) << QStringLiteral("iostream") << QStringLiteral("#include "); QTest::newRow("global-10") << QStringLiteral("#include ") << KTextEditor::Cursor(0, 14) << QStringLiteral("cstdint") << QStringLiteral("#include "); QTest::newRow("global-11") << QStringLiteral("#include ") << KTextEditor::Cursor(0, 17) << QStringLiteral("cstdint") << QStringLiteral("#include "); QTest::newRow("local-0") << QStringLiteral("#include \"") << KTextEditor::Cursor(0, 10) << QStringLiteral("foo/") << QStringLiteral("#include \"foo/\""); QTest::newRow("local-1") << QStringLiteral("#include \"foo/\"") << KTextEditor::Cursor(0, 14) << QStringLiteral("bar/") << QStringLiteral("#include \"foo/bar/\""); QTest::newRow("local-2") << QStringLiteral("#include \"foo/") << KTextEditor::Cursor(0, 14) << QStringLiteral("bar/") << QStringLiteral("#include \"foo/bar/\""); QTest::newRow("local-3") << QStringLiteral("# /* 1 */ include /* 1 */ \"\" /* 1 */") << KTextEditor::Cursor(0, 28) << QStringLiteral("foo/") << QStringLiteral("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */"); QTest::newRow("local-4") << QStringLiteral("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */") << KTextEditor::Cursor(0, 31) << QStringLiteral("bar/") << QStringLiteral("# /* 1 */ include /* 1 */ \"foo/bar/\" /* 1 */"); QTest::newRow("local-5") << QStringLiteral("#include \"foo/\"") << KTextEditor::Cursor(0, 10) << QStringLiteral("foo/") << QStringLiteral("#include \"foo/\""); QTest::newRow("local-6") << QStringLiteral("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 10) << QStringLiteral("foo/") << QStringLiteral("#include \"foo/\""); QTest::newRow("local-7") << QStringLiteral("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 14) << QStringLiteral("bar/") << QStringLiteral("#include \"foo/bar/\""); QTest::newRow("dash-1") << QStringLiteral("#include \"") << KTextEditor::Cursor(0, 10) << QStringLiteral("dash-file.h") << QStringLiteral("#include \"dash-file.h\""); QTest::newRow("dash-2") << QStringLiteral("#include \"dash-") << KTextEditor::Cursor(0, 15) << QStringLiteral("dash-file.h") << QStringLiteral("#include \"dash-file.h\""); QTest::newRow("dash-4") << QStringLiteral("#include \"dash-file.h\"") << KTextEditor::Cursor(0, 13) << QStringLiteral("dash-file.h") << QStringLiteral("#include \"dash-file.h\""); QTest::newRow("dash-5") << QStringLiteral("#include \"dash-file.h\"") << KTextEditor::Cursor(0, 14) << QStringLiteral("dash-file.h") << QStringLiteral("#include \"dash-file.h\""); QTest::newRow("dash-6") << QStringLiteral("#include \"dash-file.h\"") << KTextEditor::Cursor(0, 15) << QStringLiteral("dash-file.h") << QStringLiteral("#include \"dash-file.h\""); } void TestCodeCompletion::testIncludePathCompletion() { QFETCH(QString, code); QFETCH(KTextEditor::Cursor, cursor); QFETCH(QString, itemId); QFETCH(QString, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); QVERIFY(dir.mkpath("foo/bar/asdf")); TestFile file(code, QStringLiteral("cpp"), nullptr, tempDir.path()); { QFile otherFile(tempDir.path() + "/dash-file.h"); QVERIFY(otherFile.open(QIODevice::WriteOnly)); } IncludeTester tester(executeIncludePathCompletion(&file, cursor)); QVERIFY(tester.completionContext); QVERIFY(tester.completionContext->isValid()); auto item = tester.findItem(itemId); QVERIFY(item); auto view = createView(file.url().toUrl(), this); QVERIFY(view.get()); auto doc = view->document(); item->execute(view.get(), KTextEditor::Range(cursor, cursor)); QCOMPARE(doc->text(), result); const auto newCursor = view->cursorPosition(); QCOMPARE(newCursor.line(), cursor.line()); if (!itemId.endsWith('/')) { // file inserted, cursor should be at end of line QCOMPARE(newCursor.column(), doc->lineLength(cursor.line())); } else { // directory inserted, cursor should be before the " or > const auto cursorChar = doc->characterAt(newCursor); QVERIFY(cursorChar == '"' || cursorChar == '>'); } } void TestCodeCompletion::testIncludePathCompletionLocal() { TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); TestFile impl(QStringLiteral("#include \""), QStringLiteral("cpp"), &header); IncludeTester tester(executeIncludePathCompletion(&impl, {0, 10})); QVERIFY(tester.names.contains(header.url().toUrl().fileName())); QVERIFY(tester.names.contains("iostream")); } void TestCodeCompletion::testOverloadedFunctions() { TestFile file(QStringLiteral("void f(); int f(int); void f(int, double){\n "), QStringLiteral("cpp")); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); const auto context = createContext(top, sessionData, {1, 0}); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(context); QCOMPARE(tester.items.size(), 3); for (const auto& item : tester.items) { auto function = item->declaration()->type(); const QString display = item->declaration()->identifier().toString() + function->partToString(FunctionType::SignatureArguments); const QString itemDisplay = tester.itemData(item).toString() + tester.itemData(item, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(display, itemDisplay); } QVERIFY(tester.items[0]->declaration().data() != tester.items[1]->declaration().data()); QVERIFY(tester.items[0]->declaration().data() != tester.items[2]->declaration().data()); QVERIFY(tester.items[1]->declaration().data() != tester.items[2]->declaration().data()); } void TestCodeCompletion::testCompletionPriority() { QFETCH(QString, code); QFETCH(CompletionPriorityItems, expectedItems); executeCompletionPriorityTest(code, expectedItems); } void TestCodeCompletion::testCompletionPriority_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("pointer") << "class A{}; class B{}; class C : public B{}; int main(){A* a; B* b; C* c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Pointer to derived class is not added to the Best Matches group")}}}; QTest::newRow("class") << "class A{}; class B{}; class C : public B{}; int main(){A a; B b; C c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Derived class is not added to the Best Matches group")}}}; QTest::newRow("primary-types") << "class A{}; int main(){A a; int b; bool c = \n " << CompletionPriorityItems{{1,0}, {{"a", 0, 34}, {"b", 8, 0}, {"c", 9, 0}}}; QTest::newRow("reference") << "class A{}; class B{}; class C : public B{};" "int main(){A tmp; A& a = tmp; C tmp2; C& c = tmp2; B& b =\n ;}" << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Reference to derived class is not added to the Best Matches group")}}}; QTest::newRow("typedef") << "struct A{}; struct B{}; typedef A AA; typedef B BB; void f(A p);" "int main(){ BB b; AA a; f(\n }" << CompletionPriorityItems{{1,0}, {{"a", 9, 0}, {"b", 0, 21}}}; QTest::newRow("returnType") << "struct A{}; struct B{}; struct Test{A f();B g(); Test() { A a =\n }};" << CompletionPriorityItems{{1,0}, {{"f", 9, 0}, {"g", 0, 21}}}; QTest::newRow("template") << "template class Class{}; template class Class2{};" "int main(){ Class a; Class2 b =\n }" << CompletionPriorityItems{{1,0}, {{"b", 9, 0}, {"a", 0, 21}}}; QTest::newRow("protected-access") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void g(){\n }};" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; QTest::newRow("protected-access2") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void f();};" "void Derived::f(){\n }" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; } void TestCodeCompletion::testVariableScope() { TestFile file(QStringLiteral("int var; \nvoid test(int var) {int tmp =\n }"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); const auto context = createContext(top, sessionData, {2, 0}); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(context); QCOMPARE(tester.items.size(), 4); auto item = tester.findItem(QStringLiteral("var")); VERIFY(item); QCOMPARE(item->declaration()->range().start, CursorInRevision(1, 14)); } struct HintItem { QString hint; bool hasDeclaration; bool operator==(const HintItem& rhs) const { return std::tie(hint, hasDeclaration) == std::tie(rhs.hint, rhs.hasDeclaration); } bool operator<(const HintItem& rhs) const { return std::tie(hint, hasDeclaration) < std::tie(rhs.hint, rhs.hasDeclaration); } QByteArray toString() const { return "HintItem(" + hint.toUtf8() + ", " + (hasDeclaration ? "true" : "false") + ')'; } }; -Q_DECLARE_METATYPE(HintItem); +Q_DECLARE_METATYPE(HintItem) using HintItemList = QVector; namespace QTest { template<> char *toString(const HintItem& hint) { return qstrdup(hint.toString()); } template<> char *toString(const HintItemList& hints) { QByteArray ba = "HintItemList("; for (int i = 0, c = hints.size(); i < c; ++i) { ba += hints[i].toString(); if (i == c - 1) { ba += ')'; } else { ba += ", "; } } return qstrdup(ba.constData()); } } void TestCodeCompletion::testArgumentHintCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); QFETCH(HintItemList, hints); executeCompletionTest(code, expectedItems, NoMacroOrBuiltin, [&](const ClangCodeCompletionItemTester& tester) { HintItemList actualHints; for (const auto& item : tester.items) { if (item->argumentHintDepth() == 1) { actualHints << HintItem{ tester.itemData(item).toString() + tester.itemData(item, KTextEditor:: CodeCompletionModel::Arguments).toString(), item->declaration() }; } } std::sort(hints.begin(), hints.end()); std::sort(actualHints.begin(), actualHints.end()); QEXPECT_FAIL("member function", "clang_getCompletionParent returns nothing, thus decl lookup fails", Continue); QEXPECT_FAIL("namespaced function", "clang_getCompletionParent returns nothing, thus decl lookup fails", Continue); QEXPECT_FAIL("namespaced constructor", "clang_getCompletionParent returns nothing, thus decl lookup fails", Continue); QCOMPARE(actualHints, hints); }); } void TestCodeCompletion::testArgumentHintCompletion_data() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif qRegisterMetaType("HintItemList"); QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::addColumn("hints"); QTest::newRow("global function") << "void foo(int);\n" "int main() { \nfoo( " << CompletionItems{{2,4}, { "foo", "foo", "main" }} << HintItemList{{"foo(int)", true}}; QTest::newRow("namespaced function") << "namespace ns { void foo(int); }\n" "int main() { \nns::foo( " << CompletionItems{{2,4}, { "foo" }} << HintItemList{{"foo(int)", true}}; QTest::newRow("member function") << "struct Struct{ void foo(int);}\n" "int main() {Struct s; \ns.foo( " << CompletionItems{{2,6}, { "Struct", "foo", "main", "s" }} << HintItemList{{"foo(int)", true}}; QTest::newRow("template function") << "template void foo(T);\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "main" }} << HintItemList{{"foo(T)", true}}; QTest::newRow("overloaded functions") << "void foo(int); void foo(int, double)\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "foo", "foo", "main" }} << HintItemList{ {"foo(int)", true}, {"foo(int, double)", true} }; QTest::newRow("overloaded functions2") << "void foo(int); void foo(int, double)\n" "int main() { foo(1,\n " << CompletionItems{{2,1}, { "foo", "foo", "foo", "main" }} << HintItemList{{"foo(int, double)", true}}; QTest::newRow("constructor") << "struct foo { foo(int); foo(int, double); }\n" "int main() { foo f(\n " << CompletionItems{{2,1}, { "f", "foo", "foo", "foo", "foo", "foo", "main" }} << HintItemList{ {"foo(int)", true}, {"foo(int, double)", true}, {"foo(foo &&)", false}, {"foo(const foo &)", false} }; QTest::newRow("constructor2") << "struct foo { foo(int); foo(int, double); }\n" "int main() { foo f(1,\n " << CompletionItems{{2,1}, { "f", "foo", "foo", "main" }} << HintItemList{ {"foo(int, double)", true} }; QTest::newRow("namespaced constructor") << "namespace ns { struct foo { foo(int); foo(int, double); } }\n" "int main() { ns::foo f(\n " << CompletionItems{{2,1}, { "f", "foo", "foo", "foo", "foo", "main", "ns" }} << HintItemList{ {"foo(int)", true}, {"foo(int, double)", true}, {"foo(ns::foo &&)", false}, {"foo(const ns::foo &)", false} }; } void TestCodeCompletion::testArgumentHintCompletionDefaultParameters() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif TestFile file(QStringLiteral("void f(int i, int j = 0, double k =1){\nf( "), QStringLiteral("cpp")); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); const auto context = createContext(top, sessionData, {1, 2}); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(context); QExplicitlySharedDataPointer f; for (const auto& item : tester.items) { if (item->argumentHintDepth() == 1) { f = item; break; } } QVERIFY(f.data()); const QString itemDisplay = tester.itemData(f).toString() + tester.itemData(f, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(itemDisplay, QStringLiteral("f(int i, int j = 0, double k = 1)")); } void TestCodeCompletion::testCompleteFunction() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); QFETCH(QString, itemToExecute); QFETCH(QString, expectedCode); auto executeItem = [=] (const ClangCodeCompletionItemTester& tester) { auto item = tester.findItem(itemToExecute); QVERIFY(item); auto view = createView(tester.completionContext->duContext()->url().toUrl(), this); item->execute(view.get(), view->document()->wordRangeAt(expectedItems.position)); QCOMPARE(view->document()->text(), expectedCode); }; executeCompletionTest(code, expectedItems, NoMacroOrBuiltin, executeItem); } void TestCodeCompletion::testCompleteFunction_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::addColumn("itemToExecute"); QTest::addColumn("expectedCode"); QTest::newRow("add-parens") << "int foo();\nint main() {\n\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "foo" << "int foo();\nint main() {\nfoo()\n}"; QTest::newRow("keep-parens") << "int foo();\nint main() {\nfoo();\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "main" << "int foo();\nint main() {\nmain();\n}"; QTest::newRow("bug375635") << "enum class Color {\nBlue, Green, Red, Yellow\n};\nvoid foo() {\nColor x;\nswitch (x) {\ncase : break;}\n}" << CompletionItems({6, 5}, {"Blue", "Green", "Red", "Yellow"}) << "Yellow" << "enum class Color {\nBlue, Green, Red, Yellow\n};\nvoid foo() {\nColor x;\nswitch (x) {\ncase Color::Yellow: break;}\n}"; QTest::newRow("bug368544") << "class Klass {\npublic:\ntemplate \nvoid func(int a, T x, int b) const;\n};\n" << CompletionItems({5, 0}, {"Klass", "Klass::func(int a, T x, int b) const"}) << "Klass::func(int a, T x, int b) const" << "class Klass {\npublic:\ntemplate \nvoid func(int a, T x, int b) const;\n};\ntemplate void Klass::func(int a, T x, int b) const\n{\n}\n"; QTest::newRow("bug377397") << "template class Foo {\nvoid bar();\n};\n" << CompletionItems({3, 0}, {"Foo", "Foo::bar()"}) << "Foo::bar()" << "template class Foo {\nvoid bar();\n};\ntemplate void Foo::bar()\n{\n}\n"; QTest::newRow("template-template-parameter") << "template class X, typename B>\nclass Test {\npublic:\nvoid bar(B a);\n};\n" << CompletionItems({5, 0}, {"Test", "Test::bar(B a)"}) << "Test::bar(B a)" << "template class X, typename B>\nclass Test {\npublic:\nvoid bar(B a);\n};\ntemplate class X, typename B> void Test::bar(B a)\n{\n}\n"; } void TestCodeCompletion::testIgnoreGccBuiltins() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { TestFile file(QLatin1String(""), QStringLiteral("cpp"), project, dir.path()); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), {}); } } diff --git a/plugins/clang/tests/test_duchain.cpp b/plugins/clang/tests/test_duchain.cpp index 360ff9a714..e4b830e333 100644 --- a/plugins/clang/tests/test_duchain.cpp +++ b/plugins/clang/tests/test_duchain.cpp @@ -1,2089 +1,2089 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include #include #include #include #include -QTEST_MAIN(TestDUChain); +QTEST_MAIN(TestDUChain) using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: ~TestEnvironmentProvider() override = default; QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } Path::List includesInBackground(const QString& /*path*/) const override { return includes; } Path::List frameworkDirectoriesInBackground(const QString&) const override { return {}; } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); const auto plainText = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); // if comment is e.g. "("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; // as long as https://bugs.llvm.org/show_bug.cgi?id=35333 is not fixed, we don't fully parse and render // doxygen-style comments properly (cf. `makeComment` in builder.cpp) #define PARSE_COMMENTS 0 QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); #endif QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); #endif QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); // NOTE: header is _not_ explicitly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("cpp"), &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } void TestDUChain::testMissingInclude() { auto code = R"( #pragma once #include "missing1.h" template class A { T a; }; #include "missing2.h" class B : public A { }; )"; // NOTE: This fails and needs fixing. If the include of "missing2.h" // above is commented out, then it doesn't fail. Maybe // clang stops processing when it encounters the second missing // header, or similar. TestFile header(code, QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"), &header); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = impl.topContext(); QVERIFY(top); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); QVERIFY(headerCtx); QCOMPARE(headerCtx->url(), header.url()); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Second missing header isn't reported", Continue); #endif QCOMPARE(headerCtx->problems().count(), 2); QCOMPARE(headerCtx->localDeclarations().count(), 2); auto a = dynamic_cast(headerCtx->localDeclarations().first()); QVERIFY(a); auto b = dynamic_cast(headerCtx->localDeclarations().last()); QVERIFY(b); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue); #endif QCOMPARE(b->baseClassesSize(), 1u); #if CINDEX_VERSION_MINOR < 34 // at least the one problem we have should have been propagated QCOMPARE(top->problems().count(), 1); #else // two errors: // /tmp/testfile_f32415.h:3:10: error: 'missing1.h' file not found // /tmp/testfile_f32415.h:11:10: error: 'missing2.h' file not found QCOMPARE(top->problems().count(), 2); #endif } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), QStringLiteral("h")); TestFile header2(createCode("Header2", 1000), QStringLiteral("h")); TestFile header3(createCode("Header3", 1000), QStringLiteral("h")); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "#include \"" + header3.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("cpp")); TestFile impl2("#include \"" + header2.url().str() + "\"\n" "#include \"" + header1.url().str() + "\"\n" "#include \"" + header3.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("cpp")); TestFile impl3("#include \"" + header3.url().str() + "\"\n" "#include \"" + header1.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "int main() { return 0; }", QStringLiteral("cpp")); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file(QStringLiteral("int main() { int i = 42; return i; }"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents(QStringLiteral("int main()\n{\nfloat i = 13; return i - 5;\n}\n")); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file(QStringLiteral("int i = 1 / 0;\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents(QStringLiteral("int i = 0;\n")); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file(QStringLiteral(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 4); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier(QStringLiteral("foo")))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif decl = ctx->findDeclarations(QualifiedIdentifier(QStringLiteral("autoTemplRefParam")))[0]; QVERIFY(decl); QVERIFY(decl->abstractType()); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue); #endif QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >")); } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file(QStringLiteral("template struct foo { T member; } foo f; auto i = f.member;"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = nullptr; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo< T >")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file(QStringLiteral("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file(QStringLiteral("class Base {}; class Inherited : public Base {};"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file(QStringLiteral("struct a{}; struct b : a {};\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file(QStringLiteral("template struct a{}; struct b : a {};\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGetInheriters_data() { QTest::addColumn("code"); QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};"; QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};"; } void TestDUChain::testGetInheriters() { QFETCH(QString, code); TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); DUContext* baseCtx = baseDecl->internalContext(); QVERIFY(baseCtx); QCOMPARE(baseCtx->localDeclarations().count(), 1); Declaration* innerDecl = baseCtx->localDeclarations().first(); QCOMPARE(innerDecl->identifier(), Identifier("Inner")); if (auto forward = dynamic_cast(innerDecl)) { innerDecl = forward->resolve(top); } QVERIFY(dynamic_cast(innerDecl)); Declaration* inheritedDecl = top->localDeclarations().last(); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); uint maxAllowedSteps = uint(-1); auto baseInheriters = DUChainUtils::getInheriters(baseDecl, maxAllowedSteps); QCOMPARE(baseInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto innerInheriters = DUChainUtils::getInheriters(innerDecl, maxAllowedSteps); QCOMPARE(innerInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto inheritedInheriters = DUChainUtils::getInheriters(inheritedDecl, maxAllowedSteps); QCOMPARE(inheritedInheriters.count(), 0); } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file(QStringLiteral("void foo(int arg1, char arg2);\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file(QStringLiteral("void func(); void func() {}\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file(QStringLiteral("struct SomeStruct {} s;\n"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier(QStringLiteral("SomeStruct"))); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path(QStringLiteral("/foo/bar/baz"))); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path(QStringLiteral("/lalalala"))); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } QVERIFY(!QFileInfo::exists(url.toLocalFile())); QFile file(url.toLocalFile()); file.open(QIODevice::WriteOnly); Q_ASSERT(file.isOpen()); auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testActiveDocumentsGetBestPriority() { // note: this test would make more sense in kdevplatform, but we don't have a language plugin available there // (required for background parsing) // TODO: Create a fake-language plugin in kdevplatform for testing purposes, use that. TestFile file1(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); TestFile file2(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); TestFile file3(QStringLiteral("int main() {}\n"), QStringLiteral("cpp")); DUChain::self()->storeToDisk(); auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file1.url())); auto documentController = ICore::self()->documentController(); // open first document (no activation) auto doc = documentController->openDocument(file1.url().toUrl(), KTextEditor::Range::invalid(), {IDocumentController::DoNotActivate}); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file1.url())); QCOMPARE(backgroundParser->priorityForDocument(file1.url()), (int)BackgroundParser::NormalPriority); // open second document, activate doc = documentController->openDocument(file2.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file2.url())); QCOMPARE(backgroundParser->priorityForDocument(file2.url()), (int)BackgroundParser::BestPriority); // open third document, activate, too doc = documentController->openDocument(file3.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file3.url())); QCOMPARE(backgroundParser->priorityForDocument(file3.url()), (int)BackgroundParser::BestPriority); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("cpp")); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto dec = file.topContext()->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("cpp")); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); QCOMPARE(ctx->childContexts().size(), 2); QCOMPARE(ctx->localDeclarations().size(), 2); auto dec = ctx->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file.url())); QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); spy.wait(); doc->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("cpp"), &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "int main() { return foo(); }", QStringLiteral("cpp"), &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert(QStringLiteral("foooooooo"), QStringLiteral("baaar!")); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path(QStringLiteral("/foo/bar/asdf/lalala"))); } // 3) stop } } void TestDUChain::testMacroDependentHeader() { TestFile header(QStringLiteral("struct MY_CLASS { class Q{Q(); int m;}; int m; };\n"), QStringLiteral("h")); TestFile impl("#define MY_CLASS A\n" "#include \"" + header.url().str() + "\"\n" "#undef MY_CLASS\n" "#define MY_CLASS B\n" "#include \"" + header.url().str() + "\"\n" "#undef MY_CLASS\n" "A a;\n" "const A::Q aq;\n" "B b;\n" "const B::Q bq;\n" "int am = a.m;\n" "int aqm = aq.m;\n" "int bm = b.m;\n" "int bqm = bq.m;\n" , QStringLiteral("cpp"), &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 10); // 2x macro, then a, aq, b, bq QCOMPARE(top->importedParentContexts().size(), 1); AbstractType::Ptr type = top->localDeclarations()[2]->abstractType(); StructureType* sType = dynamic_cast(type.data()); QVERIFY(sType); QCOMPARE(sType->toString(), QString("A")); Declaration* decl = sType->declaration(top); QVERIFY(decl); AbstractType::Ptr type2 = top->localDeclarations()[4]->abstractType(); StructureType* sType2 = dynamic_cast(type2.data()); QVERIFY(sType2); QCOMPARE(sType2->toString(), QString("B")); Declaration* decl2 = sType2->declaration(top); QVERIFY(decl2); TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); QVERIFY(top2); QCOMPARE(top2->localDeclarations().size(), 2); QCOMPARE(top2->localDeclarations()[0], decl); QCOMPARE(top2->localDeclarations()[1], decl2); qDebug() << "DECL RANGE:" << top2->localDeclarations()[0]->range().castToSimpleRange(); qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange(); // validate uses: QCOMPARE(top->usesCount(), 14); QCOMPARE(top->uses()[0].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); QCOMPARE(top->uses()[1].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A")); QCOMPARE(top->uses()[2].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q")); QCOMPARE(top->uses()[3].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); QCOMPARE(top->uses()[4].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B")); QCOMPARE(top->uses()[5].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q")); QCOMPARE(top->uses()[6].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->uses()[7].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::m")); QCOMPARE(top->uses()[8].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("aq")); QCOMPARE(top->uses()[9].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q::m")); QCOMPARE(top->uses()[10].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->uses()[11].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::m")); QCOMPARE(top->uses()[12].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("bq")); QCOMPARE(top->uses()[13].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q::m")); } void TestDUChain::testHeaderParsingOrder1() { TestFile header(QStringLiteral("typedef const A B;\n"), QStringLiteral("h")); TestFile impl("template class A{};\n" "#include \"" + header.url().str() + "\"\n" "B c;", QStringLiteral("cpp"), &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->importedParentContexts().size(), 1); AbstractType::Ptr type = top->localDeclarations()[1]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); // this declaration could be resolved, because it was created with an // indirect DeclarationId that is resolved from the perspective of 'top' Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); QCOMPARE(decl, top->localDeclarations()[0]); // now ensure that a use was build for 'A' in header1 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(top)); QVERIFY(top2); QEXPECT_FAIL("", "the use could not be created because the corresponding declaration didn't exist yet", Continue); QCOMPARE(top2->usesCount(), 1); // Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); // QVERIFY(decl2); // QCOMPARE(decl, decl2); } void TestDUChain::testHeaderParsingOrder2() { TestFile header(QStringLiteral("template class A{};\n"), QStringLiteral("h")); TestFile header2(QStringLiteral("typedef const A B;\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n" "#include \"" + header2.url().str() + "\"\n" "B c;", QStringLiteral("cpp"), &header); impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(500000)); DUChainReadLocker lock; TopDUContext* top = impl.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->importedParentContexts().size(), 2); AbstractType::Ptr type = top->localDeclarations()[0]->abstractType(); TypeAliasType* aType = dynamic_cast(type.data()); QVERIFY(aType); AbstractType::Ptr targetType = aType->type(); QVERIFY(targetType); IdentifiedType *idType = dynamic_cast(targetType.data()); QVERIFY(idType); Declaration* decl = idType->declaration(top); // NOTE: the decl. doesn't know (yet) about the template insantiation QVERIFY(decl); // now ensure that a use was build for 'A' in header2 TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[1].context(top)); QVERIFY(top2); QCOMPARE(top2->usesCount(), 1); Declaration* decl2 = top2->uses()[0].usedDeclaration(top2); QCOMPARE(decl, decl2); } void TestDUChain::testMacrosRanges() { TestFile file(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMacroUses() { TestFile file(QStringLiteral("#define USER(x) x\n#define USED\nUSER(USED)"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition1 = file.topContext()->localDeclarations()[0]; auto macroDefinition2 = file.topContext()->localDeclarations()[1]; QCOMPARE(macroDefinition1->uses().size(), 1); QCOMPARE(macroDefinition1->uses().begin()->first(), RangeInRevision(2,0,2,4)); #if CINDEX_VERSION_MINOR < 32 QEXPECT_FAIL("", "This appears to be a clang bug, the AST doesn't contain the macro use", Continue); #endif QCOMPARE(macroDefinition2->uses().size(), 1); if (macroDefinition2->uses().size()) { QCOMPARE(macroDefinition2->uses().begin()->first(), RangeInRevision(2,5,2,9)); } } void TestDUChain::testMultiLineMacroRanges() { TestFile file(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file(QStringLiteral("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B(QStringLiteral("#pragma once\nint B();\n"), QStringLiteral("h")); TestFile C("#pragma once\n#include \"" + B.url().str() + "\"\nint C();\n", QStringLiteral("h")); TestFile A("#include \"" + B.url().str() + "\"\n" + "#include \"" + C.url().str() + "\"\nint A();\n", QStringLiteral("cpp")); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file(QStringLiteral("int main();\n"), QStringLiteral("cpp")); m_provider->includes.clear(); m_provider->includes.append(Path(QStringLiteral("/path1"))); m_provider->includes.append(Path(QStringLiteral("/path2"))); m_provider->defines.clear(); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2")); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3")); m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1")); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path(QStringLiteral("/path2"))); m_provider->includes.append(Path(QStringLiteral("/path1"))); } } } void TestDUChain::testReparseMacro() { TestFile file(QStringLiteral("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;"), QStringLiteral("cpp")); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file(QStringLiteral("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file(QStringLiteral("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function() { return {}; } int main () { functionTemplate(int()); function(); } )"; TestFile file(code, QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier(QStringLiteral("functionTemplate"))); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier(QStringLiteral("function"))); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header(QStringLiteral("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n"), QStringLiteral("h")); TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"), &header); QVERIFY(impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST ))); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } QVERIFY(impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive))); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } void TestDUChain::testExternC() { auto code = R"(extern "C" { void foo(); })"; TestFile file(code, QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); } void TestDUChain::testReparseUnchanged_data() { QTest::addColumn("headerCode"); QTest::addColumn("implCode"); QTest::newRow("include-guards") << R"( #ifndef GUARD #define GUARD int something; #endif )" << R"( #include "%1" )"; QTest::newRow("template-default-parameters") << R"( #ifndef TEST_H #define TEST_H template class dummy; template class dummy { int field[T]; }; #endif )" << R"( #include "%1" int main(int, char **) { dummy<> x; (void)x; } )"; } void TestDUChain::testReparseUnchanged() { QFETCH(QString, headerCode); QFETCH(QString, implCode); TestFile header(headerCode, QStringLiteral("h")); TestFile impl(implCode.arg(header.url().str()), QStringLiteral("cpp"), &header); auto checkProblems = [&] (bool reparsed) { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(headerCtx->problems().isEmpty()); auto implCtx = DUChain::self()->chainForDocument(impl.url()); QVERIFY(implCtx); if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) { QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue); } QVERIFY(implCtx->problems().isEmpty()); }; impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); checkProblems(false); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); checkProblems(true); } void TestDUChain::testTypeAliasTemplate() { TestFile file(QStringLiteral("template using Alias = T; using Foo = Alias;"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto templateAlias = file.topContext()->localDeclarations().first(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->isTypeAlias()); QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("Alias")); QCOMPARE(templateAlias->uses().size(), 1); QCOMPARE(templateAlias->uses().first().size(), 1); QCOMPARE(templateAlias->uses().first().first(), RangeInRevision(0, 51, 0, 56)); } void TestDUChain::testDeclarationsInsideMacroExpansion() { TestFile header(QStringLiteral("#define DECLARE(a) typedef struct a##__ {int var;} *a\nDECLARE(D);\n"), QStringLiteral("h")); TestFile file("#include \"" + header.url().str() + "\"\nint main(){\nD d; d->var;}\n", QStringLiteral("cpp")); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto context = file.topContext()->childContexts().first()->childContexts().first(); QVERIFY(context); QCOMPARE(context->localDeclarations().size(), 1); QCOMPARE(context->usesCount(), 3); QCOMPARE(context->uses()[0].m_range, RangeInRevision({2, 0}, {2, 1})); QCOMPARE(context->uses()[1].m_range, RangeInRevision({2, 5}, {2, 6})); QCOMPARE(context->uses()[2].m_range, RangeInRevision({2, 8}, {2, 11})); } // see also: https://bugs.kde.org/show_bug.cgi?id=368067 void TestDUChain::testForwardTemplateTypeParameterContext() { TestFile file(QStringLiteral(R"( template class Foo; class MatchingName { void bar(); }; void MatchingName::bar() { } )"), QStringLiteral("cpp")); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); } // see also: https://bugs.kde.org/show_bug.cgi?id=368460 void TestDUChain::testTemplateFunctionParameterName() { TestFile file(QStringLiteral(R"( template void foo(int name); void bar(int name); )"), QStringLiteral("cpp")); file.parse(); QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; const auto top = file.topContext(); QVERIFY(top); DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems)); dumper.dump(top); auto declarations = top->localDeclarations(); QCOMPARE(declarations.size(), 2); for (auto decl : declarations) { auto ctx = DUChainUtils::getArgumentContext(decl); QVERIFY(ctx); auto args = ctx->localDeclarations(); if (decl == declarations.first()) QEXPECT_FAIL("", "We get two declarations, for both template and args :(", Continue); QCOMPARE(args.size(), 1); if (decl == declarations.first()) QEXPECT_FAIL("", "see above, this then triggers T T here", Continue); QCOMPARE(args.first()->toString(), QStringLiteral("int name")); } } static bool containsErrors(const QList& problems) { auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) { return problem->severity() == Problem::Error; }); return it != problems.end(); } static bool expectedXmmintrinErrors(const QList& problems) { foreach (const auto& problem, problems) { if (problem->severity() == Problem::Error && !problem->description().contains(QLatin1String("Cannot initialize a parameter of type"))) { return false; } } return true; } static void verifyNoErrors(TopDUContext* top, QSet& checked) { const auto problems = top->problems(); if (containsErrors(problems)) { qDebug() << top->url() << top->problems(); if (top->url().str().endsWith(QLatin1String("xmmintrin.h")) && expectedXmmintrinErrors(problems)) { QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue); QVERIFY(false); } else { QFAIL("parse error detected"); } } const auto imports = top->importedParentContexts(); foreach (const auto& import, imports) { auto ctx = import.context(top); QVERIFY(ctx); auto importedTop = ctx->topContext(); if (checked.contains(importedTop)) { continue; } checked.insert(importedTop); verifyNoErrors(importedTop, checked); } } void TestDUChain::testFriendDeclaration() { TestFile file(QStringLiteral(R"( struct FriendFoo { friend class FriendBar; }; class FriendBar{}; FriendBar friendBar; )"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto friendBar = file.topContext()->localDeclarations()[1]; if (CINDEX_VERSION_MINOR < 37) { QEXPECT_FAIL("", "Your clang version is too old", Abort); } QCOMPARE(friendBar->uses().size(), 1); QCOMPARE(friendBar->uses().begin()->first(), RangeInRevision(3,25,3,34)); QCOMPARE(friendBar->uses().begin()->last(), RangeInRevision(8,8,8,17)); } } void TestDUChain::testVariadicTemplateArguments() { TestFile file(QStringLiteral(R"( template class VariadicTemplate {}; VariadicTemplate variadic; )"), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); if (CINDEX_VERSION_MINOR < 37) { QEXPECT_FAIL("", "Your clang version is too old", Abort); } QCOMPARE(decl->toString(), QStringLiteral("VariadicTemplate< int, double, bool > variadic")); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QStringLiteral("VariadicTemplate< int, double, bool >")); } } void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { // TODO: Also test in C mode. Currently it doesn't work (some intrinsics missing?) TestFile file(QStringLiteral(R"( #include int main() { return 0; } )"), QStringLiteral("cpp"), project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } void TestDUChain::testLambda() { TestFile file(QStringLiteral("auto lambda = [](int p1, int p2, int p3) { int var1, var2; };"), QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); auto lambdaContext = file.topContext()->childContexts().first(); QCOMPARE(lambdaContext->type(), DUContext::Function); QCOMPARE(lambdaContext->localDeclarations().size(), 3); QCOMPARE(lambdaContext->childContexts().size(), 1); QCOMPARE(lambdaContext->childContexts().first()->type(), DUContext::Other); QCOMPARE(lambdaContext->childContexts().first()->localDeclarations().size(), 2); } } void TestDUChain::testQtIntegration() { QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir(QStringLiteral("QtCore")); // create the file but don't put anything in it QFile header(includeDir.path() + "/QtCore/qobjectdefs.h"); QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text)); } QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); m_provider->defines.clear(); m_provider->includes = {Path(includeDir.path() + "/QtCore")}; m_projectController->addProject(project); { TestFile file(QStringLiteral(R"( #define slots #define signals #define Q_SLOTS #define Q_SIGNALS #include struct MyObject { public: void other1(); public slots: void slot1(); signals: void signal1(); private Q_SLOTS: void slot2(); Q_SIGNALS: void signal2(); public: void other2(); }; )"), QStringLiteral("cpp"), project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); const auto methods = top->childContexts().last()->localDeclarations(); QCOMPARE(methods.size(), 6); foreach(auto method, methods) { auto classFunction = dynamic_cast(method); QVERIFY(classFunction); auto id = classFunction->identifier().toString(); QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal"))); QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot"))); } } m_projectController->closeAllProjects(); } void TestDUChain::testHasInclude() { TestFile header(QStringLiteral(R"( #pragma once #if __has_include_next() // good #else #error broken c++11 setup (__has_include_next) #endif )"), QStringLiteral("h")); // NOTE: header is _not_ explicitly being parsed, instead the impl job does that TestFile file(QStringLiteral(R"( #if __has_include() // good #else #error broken c++11 setup (__has_include) #endif #include "%1" )").arg(header.url().str()), QStringLiteral("cpp")); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(1000)); { DUChainDumper dumper{DUChainDumper::DumpProblems}; DUChainReadLocker lock; QVERIFY(file.topContext()); dumper.dump(file.topContext()); QVERIFY(file.topContext()->problems().isEmpty()); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); dumper.dump(headerCtx); QVERIFY(headerCtx->problems().count() <= 1); for (const auto& problem : headerCtx->problems()) { // ignore the following error: "#include_next with absolute path [-Winclude-next-absolute-path]" "" [ (2, 12) -> (2, 30) ] QVERIFY(problem->description().contains(QLatin1String("-Winclude-next-absolute-path"))); } } } diff --git a/plugins/clang/tests/test_duchainutils.cpp b/plugins/clang/tests/test_duchainutils.cpp index db1f19d1f7..2f6c83aa76 100644 --- a/plugins/clang/tests/test_duchainutils.cpp +++ b/plugins/clang/tests/test_duchainutils.cpp @@ -1,80 +1,80 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_duchainutils.h" #include #include #include #include #include #include "../duchain/duchainutils.h" #include #include using namespace KDevelop; -QTEST_GUILESS_MAIN(TestDUChainUtils); +QTEST_GUILESS_MAIN(TestDUChainUtils) void TestDUChainUtils::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestDUChainUtils::cleanupTestCase() { TestCore::shutdown(); } void TestDUChainUtils::getFunctionSignatureRange() { QFETCH(QString, code); QFETCH(KTextEditor::Range, expectedRange); { TestFile file(code, QStringLiteral("cpp")); file.parse(); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); const auto functionDecl = file.topContext()->localDeclarations()[0]; const auto range = ClangIntegration::DUChainUtils::functionSignatureRange(functionDecl); QCOMPARE(range, expectedRange); } } void TestDUChainUtils::getFunctionSignatureRange_data() { QTest::addColumn("code"); QTest::addColumn("expectedRange"); QTest::newRow("function-declaration") << "void func(\nint a, int b\n);\n" << KTextEditor::Range(0, 0, 2, 1); QTest::newRow("function-definition") << "void func(\nint a, int b\n) {}\n" << KTextEditor::Range(0, 0, 2, 2); } diff --git a/plugins/clang/tests/test_problems.cpp b/plugins/clang/tests/test_problems.cpp index 8d5f66701f..63fc485bfe 100644 --- a/plugins/clang/tests/test_problems.cpp +++ b/plugins/clang/tests/test_problems.cpp @@ -1,506 +1,506 @@ /************************************************************************************* * Copyright (C) Kevin Funk * * * * 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 "test_problems.h" #include "../duchain/clangindex.h" #include "../duchain/clangproblem.h" #include "../duchain/parsesession.h" #include "../duchain/unknowndeclarationproblem.h" #include "../util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -Q_DECLARE_METATYPE(KDevelop::IProblem::Severity); +Q_DECLARE_METATYPE(KDevelop::IProblem::Severity) using namespace KDevelop; namespace { const QString FileName = #ifdef Q_OS_WIN QStringLiteral("C:/tmp/stdin.cpp"); #else QStringLiteral("/tmp/stdin.cpp"); #endif QList parse(const QByteArray& code) { ClangIndex index; ClangParsingEnvironment environment; environment.setTranslationUnitUrl(IndexedString(FileName)); ParseSession session(ParseSessionData::Ptr(new ParseSessionData({UnsavedFile(FileName, {code})}, &index, environment))); return session.problemsForFile(session.mainFile()); } void compareFixitWithoutDescription(const ClangFixit& a, const ClangFixit& b) { QCOMPARE(a.replacementText, b.replacementText); QCOMPARE(a.range, b.range); QCOMPARE(a.currentText, b.currentText); } void compareFixitsWithoutDescription(const ClangFixits& a, const ClangFixits& b) { if (a.size() != b.size()) { qDebug() << "a:" << a; qDebug() << "b:" << b; } QCOMPARE(a.size(), b.size()); const int size = a.size(); for (int i = 0; i < size; ++i) { compareFixitWithoutDescription(a.at(i), b.at(i)); } } } QTEST_GUILESS_MAIN(TestProblems) void TestProblems::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); } void TestProblems::cleanupTestCase() { TestCore::shutdown(); } void TestProblems::testNoProblems() { const QByteArray code = "int main() {}"; auto problems = parse(code); QCOMPARE(problems.size(), 0); } void TestProblems::testBasicProblems() { // expected: // :1:13: error: expected ';' after class // class Foo {} // ^ // ; const QByteArray code = "class Foo {}"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 12)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 12)); } void TestProblems::testBasicRangeSupport() { // expected: // :1:17: warning: expression result unused [-Wunused-value] // int main() { (1 + 1); } // ~ ^ ~ const QByteArray code = "int main() { (1 + 1); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems[0]->diagnostics().size(), 0); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(0, 14)); QCOMPARE(range.end(), KTextEditor::Cursor(0, 19)); } void TestProblems::testChildDiagnostics() { // expected: // test.cpp:3:14: error: call to 'foo' is ambiguous // int main() { foo(0); } // ^~~ // test.cpp:1:6: note: candidate function // void foo(unsigned int); // ^ // test.cpp:2:6: note: candidate function // void foo(const char*); // ^ const QByteArray code = "void foo(unsigned int);\n" "void foo(const char*);\n" "int main() { foo(0); }"; auto problems = parse(code); QCOMPARE(problems.size(), 1); auto range = problems[0]->rangeInCurrentRevision(); QCOMPARE(range.start(), KTextEditor::Cursor(2, 13)); QCOMPARE(range.end(), KTextEditor::Cursor(2, 16)); QCOMPARE(problems[0]->diagnostics().size(), 2); IProblem::Ptr p1 = problems[0]->diagnostics()[0]; const ProblemPointer d1 = ProblemPointer(dynamic_cast(p1.data())); QCOMPARE(d1->url().str(), FileName); QCOMPARE(d1->rangeInCurrentRevision(), KTextEditor::Range(0, 5, 0, 8)); IProblem::Ptr p2 = problems[0]->diagnostics()[1]; const ProblemPointer d2 = ProblemPointer(dynamic_cast(p2.data())); QCOMPARE(d2->url().str(), FileName); QCOMPARE(d2->rangeInCurrentRevision(), KTextEditor::Range(1, 5, 1, 8)); } -Q_DECLARE_METATYPE(QVector); +Q_DECLARE_METATYPE(QVector) /** * Provides a list of possible fixits: http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html */ void TestProblems::testFixits() { QFETCH(QString, code); QFETCH(int, problemsCount); QFETCH(QVector, fixits); auto problems = parse(code.toLatin1()); qDebug() << problems.last()->description(); QCOMPARE(problems.size(), problemsCount); const ClangProblem* p1 = dynamic_cast(problems[0].data()); QVERIFY(p1); ClangFixitAssistant* a1 = qobject_cast(p1->solutionAssistant().data()); QVERIFY(a1); QCOMPARE(p1->allFixits(), fixits); } void TestProblems::testFixits_data() { QTest::addColumn("code"); // input QTest::addColumn("problemsCount"); QTest::addColumn>("fixits"); // expected: // test -Wextra-tokens // /home/krf/test.cpp:2:8: warning: extra tokens at end of #endif directive [-Wextra-tokens] // #endif FOO // ^ // // QTest::newRow("extra-tokens test") << "#ifdef FOO\n#endif FOO\n" << 1 << QVector{ ClangFixit{"//", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 7, 1, 7)), QString()} }; // expected: // test.cpp:1:19: warning: empty parentheses interpreted as a function declaration [-Wvexing-parse] // int a(); // ^~ // test.cpp:1:19: note: replace parentheses with an initializer to declare a variable // int a(); // ^~ // = 0 QTest::newRow("vexing-parse test") << "int main() { int a(); }\n" << 1 << QVector{ ClangFixit{" = 0", DocumentRange(IndexedString(FileName), KTextEditor::Range(0, 18, 0, 20)), QString()} }; // expected: // test.cpp:2:21: error: no member named 'someVariablf' in 'C'; did you mean 'someVariable'? // int main() { C c; c.someVariablf = 1; } // ^~~~~~~~~~~~ // someVariable QTest::newRow("spell-check test") << "class C{ int someVariable; };\n" "int main() { C c; c.someVariablf = 1; }\n" << 1 << QVector{ ClangFixit{"someVariable", DocumentRange(IndexedString(FileName), KTextEditor::Range(1, 20, 1, 32)), QString()} }; } struct Replacement { QString string; QString replacement; }; using Replacements = QVector; ClangFixits resolveFilenames(const ClangFixits& fixits, const Replacements& replacements) { ClangFixits ret; for (const auto& fixit : fixits) { ClangFixit copy = fixit; for (const auto& replacement : replacements) { copy.replacementText.replace(replacement.string, replacement.replacement); copy.range.document = IndexedString(copy.range.document.str().replace(replacement.string, replacement.replacement)); } ret << copy; } return ret; } void TestProblems::testMissingInclude() { QFETCH(QString, includeFileContent); QFETCH(QString, workingFileContent); QFETCH(QString, dummyFileName); QFETCH(QVector, fixits); TestFile include(includeFileContent, QStringLiteral("h")); include.parse(TopDUContext::AllDeclarationsAndContexts); QScopedPointer dummyFile; if (!dummyFileName.isEmpty()) { dummyFile.reset(new QTemporaryFile(QDir::tempPath() + dummyFileName)); QVERIFY(dummyFile->open()); workingFileContent.replace(QLatin1String("dummyInclude"), dummyFile->fileName()); } TestFile workingFile(workingFileContent, QStringLiteral("cpp")); workingFile.parse(TopDUContext::AllDeclarationsAndContexts); QCOMPARE(include.url().toUrl().adjusted(QUrl::RemoveFilename), workingFile.url().toUrl().adjusted(QUrl::RemoveFilename)); QVERIFY(include.waitForParsed()); QVERIFY(workingFile.waitForParsed()); DUChainReadLocker lock; QVERIFY(include.topContext()); TopDUContext* includeTop = DUChainUtils::contentContextFromProxyContext(include.topContext().data()); QVERIFY(includeTop); QVERIFY(workingFile.topContext()); TopDUContext* top = DUChainUtils::contentContextFromProxyContext(workingFile.topContext()); QVERIFY(top); QCOMPARE(top->problems().size(), 1); auto problem = dynamic_cast(top->problems().first().data()); auto assistant = problem->solutionAssistant(); auto clangFixitAssistant = qobject_cast(assistant.data()); QVERIFY(clangFixitAssistant); auto resolvedFixits = resolveFilenames(fixits, { {"includeFile.h", include.url().toUrl().fileName()}, {"workingFile.h", workingFile.url().toUrl().fileName()} }); compareFixitsWithoutDescription(clangFixitAssistant->fixits(), resolvedFixits); } void TestProblems::testMissingInclude_data() { QTest::addColumn("includeFileContent"); QTest::addColumn("workingFileContent"); QTest::addColumn("dummyFileName"); QTest::addColumn>("fixits"); QTest::newRow("basic") << "class A {};\n" << "int main() { A a; }\n" << QString() << QVector{ ClangFixit{"class A;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; // cf. bug 375274 QTest::newRow("ignore-moc-at-end") << "class Foo {};\n" << "#include \nint main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/moc_fooXXXXXX.cpp" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(1, 0, 1, 0)), QString()} }; QTest::newRow("ignore-moc-at-end2") << "class Foo {};\n" << "int main() { Foo foo; }\n#include \"dummyInclude\"\n" << "/fooXXXXXX.moc" << QVector{ ClangFixit{"class Foo;\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()}, ClangFixit{"#include \"includeFile.h\"\n", DocumentRange(IndexedString(QDir::tempPath() + "/workingFile.h"), KTextEditor::Range(0, 0, 0, 0)), QString()} }; } struct ExpectedTodo { QString description; KTextEditor::Cursor start; KTextEditor::Cursor end; }; typedef QVector ExpectedTodos; Q_DECLARE_METATYPE(ExpectedTodos) void TestProblems::testTodoProblems() { QFETCH(QString, code); QFETCH(ExpectedTodos, expectedTodos); TestFile file(code, QStringLiteral("cpp")); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto problems = top->problems(); QCOMPARE(problems.size(), expectedTodos.size()); for (int i = 0; i < problems.size(); ++i) { auto problem = problems[i]; auto expectedTodo = expectedTodos[i]; QCOMPARE(problem->description(), expectedTodo.description); QCOMPARE(problem->finalLocation().start(), expectedTodo.start); QCOMPARE(problem->finalLocation().end(), expectedTodo.end); } } void TestProblems::testTodoProblems_data() { QTest::addColumn("code"); QTest::addColumn("expectedTodos"); // we have two problems here: // - we cannot search for comments without declarations, // that means: we can only search inside doxygen-style comments // possible fix: -fparse-all-comments -- however: libclang API is lacking here again. // Can only search through comments attached to a valid entity in the AST // - we cannot detect the correct location of the comment yet // see more comments inside TodoExtractor QTest::newRow("simple1") << "/** TODO: Something */\n/** notodo */\n" << ExpectedTodos{{"TODO: Something", {0, 4}, {0, 19}}}; QTest::newRow("simple2") << "/// FIXME: Something\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("mixed-content") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multi-line1") << "/**\n* foo\n*\n* FIXME: Something\n*/\n" << ExpectedTodos{{"FIXME: Something", {3, 2}, {3, 18}}}; QTest::newRow("multi-line2") << "/// FIXME: Something\n///Uninteresting content\n" << ExpectedTodos{{"FIXME: Something", {0, 4}, {0, 20}}}; QTest::newRow("multiple-todos-line2") << "/**\n* FIXME: one\n*foo bar\n* FIXME: two */\n" << ExpectedTodos{ {"FIXME: one", {1, 2}, {1, 12}}, {"FIXME: two", {3, 2}, {3, 12}} }; QTest::newRow("todo-later-in-the-document") << "///foo\n\n///FIXME: bar\n" << ExpectedTodos{{"FIXME: bar", {2, 3}, {2, 13}}}; QTest::newRow("non-ascii-todo") << "/* TODO: 例えば */" << ExpectedTodos{{"TODO: 例えば", {0, 3}, {0, 12}}}; } void TestProblems::testProblemsForIncludedFiles() { TestFile header(QStringLiteral("#pragma once\n//TODO: header\n"), QStringLiteral("h")); TestFile file("#include \"" + header.url().str() + "\"\n//TODO: source\n", QStringLiteral("cpp")); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST | TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto context = DUChain::self()->chainForDocument(file.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: source")); QCOMPARE(context->problems()[0]->finalLocation().document, file.url()); context = DUChain::self()->chainForDocument(header.url()); QVERIFY(context); QCOMPARE(context->problems().size(), 1); QCOMPARE(context->problems()[0]->description(), QStringLiteral("TODO: header")); QCOMPARE(context->problems()[0]->finalLocation().document, header.url()); } } using RangeList = QVector; void TestProblems::testRanges_data() { QTest::addColumn("code"); QTest::addColumn("ranges"); { // expected: // test.cpp:4:1: error: C++ requires a type specifier for all declarations // operator[](int){return string;} // ^ // // test.cpp:4:24: error: 'string' does not refer to a value // operator[](int){return string;} // ^ const QByteArray code = "struct string{};\nclass Test{\npublic:\noperator[](int){return string;}\n};"; QTest::newRow("operator") << code << RangeList{{3, 0, 3, 8}, {3, 23, 3, 29}}; } { const QByteArray code = "#include \"/some/file/that/does/not/exist.h\"\nint main() { return 0; }"; QTest::newRow("badInclude") << code << RangeList{{0, 9, 0, 43}}; } { const QByteArray code = "int main() const\n{ return 0; }"; QTest::newRow("badConst") << code << RangeList{{0, 11, 0, 16}}; } } void TestProblems::testRanges() { QFETCH(QByteArray, code); QFETCH(RangeList, ranges); const auto problems = parse(code); RangeList actualRanges; foreach (auto problem, problems) { actualRanges << problem->rangeInCurrentRevision(); } qDebug() << actualRanges << ranges; QCOMPARE(actualRanges, ranges); } void TestProblems::testSeverity() { QFETCH(QByteArray, code); QFETCH(IProblem::Severity, severity); const auto problems = parse(code); QCOMPARE(problems.size(), 1); QCOMPARE(problems.at(0)->severity(), severity); } void TestProblems::testSeverity_data() { QTest::addColumn("code"); QTest::addColumn("severity"); QTest::newRow("error") << QByteArray("class foo {}") << IProblem::Error; QTest::newRow("warning") << QByteArray("int main() { int foo = 1 / 0; return foo; }") << IProblem::Warning; QTest::newRow("hint-unused-variable") << QByteArray("int main() { int foo = 0; return 0; }") << IProblem::Hint; QTest::newRow("hint-unused-parameter") << QByteArray("int main(int argc, char**) { return 0; }") << IProblem::Hint; } diff --git a/plugins/clang/tests/test_refactoring.cpp b/plugins/clang/tests/test_refactoring.cpp index 6145940e3a..ca3f548c4f 100644 --- a/plugins/clang/tests/test_refactoring.cpp +++ b/plugins/clang/tests/test_refactoring.cpp @@ -1,136 +1,136 @@ /* * Copyright 2017 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_refactoring.h" #include #include #include #include #include #include "codegen/clangrefactoring.h" #include #include #include #include #include #include -QTEST_MAIN(TestRefactoring); +QTEST_MAIN(TestRefactoring) using namespace KDevelop; TestRefactoring::~TestRefactoring() = default; void TestRefactoring::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestRefactoring::cleanupTestCase() { TestCore::shutdown(); } void TestRefactoring::testClassRename() { const QString codeBefore(QStringLiteral(R"( class Foo { public: Foo(); ~Foo(); }; Foo::Foo() { } Foo::~Foo() { } )")); const QString codeAfter(QStringLiteral(R"( class FooNew { public: FooNew(); ~FooNew(); }; FooNew::FooNew() { } FooNew::~FooNew() { } )")); QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); m_projectController->addProject(project); TestFile file(codeBefore, QStringLiteral("cpp"), project, dir.path()); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto declaration = top->localDeclarations().first(); QVERIFY(declaration); const QString originalName = declaration->identifier().identifier().str(); const QString newName = QStringLiteral("FooNew"); QSharedPointer collector(new BasicRefactoringCollector(declaration)); // TODO: Do this without GUI? UsesWidget uses(declaration, collector); lock.unlock(); for (int i = 0; i < 30000; i += 1000) { if (collector->isReady()) { break; } QTest::qWait(1000); } QVERIFY(collector->isReady()); BasicRefactoring::NameAndCollector nameAndCollector{newName, collector}; auto languages = ICore::self()->languageController()->languagesForUrl(file.url().toUrl()); QVERIFY(!languages.isEmpty()); auto clangLanguageSupport = languages.first(); QVERIFY(clangLanguageSupport); auto clangRefactoring = qobject_cast(clangLanguageSupport->refactoring()); QVERIFY(clangRefactoring); clangRefactoring->renameCollectedDeclarations(nameAndCollector.collector.data(), newName, originalName); QCOMPARE(file.fileContents(), codeAfter); m_projectController->closeAllProjects(); } diff --git a/plugins/clang/util/clangutils.h b/plugins/clang/util/clangutils.h index 46babaed01..f449cd761e 100644 --- a/plugins/clang/util/clangutils.h +++ b/plugins/clang/util/clangutils.h @@ -1,172 +1,172 @@ /* * Copyright 2014 Kevin Funk * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CLANGUTILS_H #define CLANGUTILS_H #include #include #include "clangprivateexport.h" #include "../duchain/unsavedfile.h" #include namespace ClangUtils { /** * Finds the most specific CXCursor which applies to the specified line and column * in the given translation unit and file. * * @param line The 0-indexed line number at which to search. * @param column The 0-indexed column number at which to search. * @param unit The translation unit to examine. * @param file The file in the translation unit to examine. * * @return The cursor at the specified location */ CXCursor getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file); enum DefaultArgumentsMode { FixedSize, ///< The vector will have length equal to the number of arguments to the function /// and any arguments without a default parameter will be represented with an empty string. MinimumSize ///< The vector will have a length equal to the number of default values }; /** * Given a cursor representing a function, returns a vector containing the string * representations of the default arguments of the function which are defined at * the occurrence of the cursor. Note that this is not necessarily all of the default * arguments of the function. * * @param cursor The cursor to examine * @return a vector of QStrings representing the default arguments, or an empty * vector if cursor does not represent a function */ QVector getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode = FixedSize); /** * @return true when the cursor kind references a named scope. */ bool isScopeKind(CXCursorKind kind); /** * @brief Retrieve a list of all unsaved files. * * @note Since this reads text from the editor widget, it must be called from the * GUI thread or with the foreground lock held. * * @return vector of all unsaved files and their current contents */ KDEVCLANGPRIVATE_EXPORT QVector unsavedFiles(); /** * Given a cursor and destination context, returns the string representing the * cursor's scope at its current location. * * @param cursor The cursor to examine * @param context The destination context from which the cursor should be referenced. * By default this will be set to the cursors lexical parent. * @return the cursor's scope as a string */ KDEVCLANGPRIVATE_EXPORT QString getScope(CXCursor cursor, CXCursor context = clang_getNullCursor()); /** * Given a cursor representing some sort of function, returns its signature. The * effect of this function when passed a non-function cursor is undefined. * * @param cursor The cursor to work with * @param scope The scope of the cursor (e.g. "SomeNS::SomeClass") * @return A QString of the function's signature */ QString getCursorSignature(CXCursor cursor, const QString& scope, const QVector& defaultArgs = QVector()); /** * Given a cursor representing the template argument list, return a * list of the argument types. * * @param cursor The cursor to work with * @return A QStringList of the template's arguments */ KDEVCLANGPRIVATE_EXPORT QStringList templateArgumentTypes(CXCursor cursor); /** * Extract the raw contents of the range @p range * * @note This will return the exact textual representation of the code, * no whitespace stripped, etc. * * TODO: It would better if we'd be able to just memcpy parts of the file buffer * that's stored inside Clang (cf. llvm::MemoryBuffer for files), but libclang * doesn't offer API for that. This implementation here is a lot more expensive. * * @param unit Translation unit this range is part of */ KDEVCLANGPRIVATE_EXPORT QByteArray getRawContents(CXTranslationUnit unit, CXSourceRange range); /** * @brief Return true if file @p file1 and file @p file2 are equal * * @see clang_File_isEqual */ inline bool isFileEqual(CXFile file1, CXFile file2) { #if CINDEX_VERSION_MINOR >= 28 return clang_File_isEqual(file1, file2); #else // note: according to the implementation of clang_File_isEqual, file1 and file2 can still be equal, // regardless of whether file1 == file2 is true or not // however, we didn't see any problems with pure pointer comparisons until now, so fall back to that return file1 == file2; #endif } /** * @brief Return true if the cursor @p cursor refers to an explicitly deleted/defaulted function * such as the default constructor in "struct Foo { Foo() = delete; }" * * TODO: do we need isExplicitlyDefaulted() + isExplicitlyDeleted()? * Currently this is only used by the implements completion to hide deleted+defaulted functions so * we don't need to know the difference. We need to tokenize the source code because there is no * such API in libclang so having one function to check both cases is more efficient (only tokenize once) */ bool isExplicitlyDefaultedOrDeleted(CXCursor cursor); /** * Extract the range of the path-spec inside the include-directive in line @p line * * Example: line = "#include " => returns {0, 10, 0, 16} * * @param originalRange This is the range that the resulting range will be based on * * @return Range pointing to the path-spec of the include or invalid range if there is no #include directive on the line. */ KDEVCLANGPRIVATE_EXPORT KTextEditor::Range rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange = KTextEditor::Range()); /** * Returns special attributes (isFinal, isQtSlot, ...) given a @p cursor representing a CXXmethod */ KDevelop::ClassFunctionFlags specialAttributes(CXCursor cursor); -}; +} #endif // CLANGUTILS_H diff --git a/plugins/cmake/cmakemanager.cpp b/plugins/cmake/cmakemanager.cpp index 5c6a11fb7a..28f79d4f5b 100644 --- a/plugins/cmake/cmakemanager.cpp +++ b/plugins/cmake/cmakemanager.cpp @@ -1,987 +1,987 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * 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 "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include "cmakeserverimportjob.h" #include "cmakeserver.h" #include "cmakeutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -Q_DECLARE_METATYPE(KDevelop::IProject*); +Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcmakemanager"), parent ) , m_filter( new ProjectFilterManager( this ) ) { if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].compilationData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } class ChooseCMakeInterfaceJob : public ExecuteCompositeJob { Q_OBJECT public: ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager) : ExecuteCompositeJob(manager, {}) , project(project) , manager(manager) { } void start() override { server = new CMakeServer(project); connect(server, &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); connect(server, &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); } private: void successfulConnection() { auto job = new CMakeServerImportJob(project, server, this); connect(job, &CMakeServerImportJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } void failedConnection(int code) { Q_ASSERT(code > 0); Q_ASSERT(!server->isServerAvailable()); server->deleteLater(); server = nullptr; // parse the JSON file CMakeImportJsonJob* job = new CMakeImportJsonJob(project, this); // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; addSubjob(manager->builder()->configure(project)); } connect(job, &CMakeImportJsonJob::result, this, [this, job]() { if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } CMakeServer* server = nullptr; IProject* const project; CMakeManager* const manager; }; KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); auto job = new ChooseCMakeInterfaceJob(project, this); connect(job, &KJob::result, this, [this, job, project](){ if (job->error() != 0) { qCWarning(CMAKE) << "couldn't load project successfully" << project->name(); m_projects.remove(project); } }); const QList jobs = { job, KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing }; Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const auto & data = m_projects[item->project()].compilationData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).frameworkDirectories; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).compileFlags; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevCMakeBuilder")); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); if (folder == project->projectItem()) { connect(job, &KJob::finished, this, [project](KJob* job) { if (job->error()) return; KDevelop::ICore::self()->projectController()->reparseProject(project, true); }); } return true; } static void populateTargets(ProjectFolderItem* folder, const QHash>& targets) { static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; QList dirTargets = kFilter>(targets[folder->path()], [](const CMakeTarget& target) -> bool { return target.type != CMakeTarget::Custom || (!target.name.endsWith(QLatin1String("_automoc")) && !target.name.endsWith(QLatin1String("_autogen")) && !standardTargets.contains(target.name) && !target.name.startsWith(QLatin1String("install/")) ); }); const auto tl = folder->targetList(); foreach (ProjectTargetItem* item, tl) { const auto idx = kIndexOf(dirTargets, [item](const CMakeTarget& target) { return target.name == item->text(); }); if (idx < 0) { delete item; } else { dirTargets.removeAt(idx); } } foreach (const auto& target, dirTargets) { switch(target.type) { case CMakeTarget::Executable: new CMakeTargetItem(folder, target.name, target.artifacts.value(0)); break; case CMakeTarget::Library: new ProjectLibraryTargetItem(folder->project(), target.name, folder); break; case CMakeTarget::Custom: new ProjectTargetItem(folder->project(), target.name, folder); break; } } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project) { if (data.m_server) { connect(data.m_server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { serverResponse(project, response); }); } else { connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); } m_projects[project] = data; populateTargets(project->projectItem(), data.targets); CTestUtils::createTestSuites(data.m_testSuites, data.targets, project); } void CMakeManager::serverResponse(KDevelop::IProject* project, const QJsonObject& response) { if (response[QStringLiteral("type")] == QLatin1String("signal")) { if (response[QStringLiteral("name")] == QLatin1String("dirty")) { m_projects[project].m_server->configure({}); } else qCDebug(CMAKE) << "unhandled signal response..." << project << response; } else if (response[QStringLiteral("type")] == QLatin1String("reply")) { const auto inReplyTo = response[QStringLiteral("inReplyTo")]; if (inReplyTo == QLatin1String("configure")) { m_projects[project].m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_projects[project].m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { auto &data = m_projects[project]; CMakeServerImportJob::processCodeModel(response, data); populateTargets(project->projectItem(), data.targets); } else { qCDebug(CMAKE) << "unhandled reply response..." << project << response; } } else { qCDebug(CMAKE) << "unhandled response..." << project << response; } } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); Declaration *decl=nullptr; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; decl=u.usedDeclaration(top->topContext()); } } CMakeNavigationWidget* doc=nullptr; if(decl) { doc=new CMakeNavigationWidget(top, decl); } else { const IDocument* d=ICore::self()->documentController()->documentForUrl(url); const KTextEditor::Document* e=d->textDocument(); KTextEditor::Cursor start=position, end=position, step(0,1); for(QChar i=e->characterAt(start); i.isLetter() || i=='_'; i=e->characterAt(start-=step)) {} start+=step; for(QChar i=e->characterAt(end); i.isLetter() || i=='_'; i=e->characterAt(end+=step)) {} QString id=e->text(KTextEditor::Range(start, end)); ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { IDocumentation::Ptr desc=docu->description(id, url); if(desc) { doc=new CMakeNavigationWidget(top, desc); } } } return doc; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qCWarning(CMAKE) << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+"/CMakeLists.txt")) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } void CMakeManager::reloadProjects() { for(IProject* project: m_projects.keys()) { CMake::checkForNeedingConfigure(project); reload(project->projectItem()); } } #include "cmakemanager.moc" diff --git a/plugins/cmake/parser/cmaketypes.h b/plugins/cmake/parser/cmaketypes.h index 9537193f17..b4d11a9910 100644 --- a/plugins/cmake/parser/cmaketypes.h +++ b/plugins/cmake/parser/cmaketypes.h @@ -1,88 +1,88 @@ /* Copyright 2009 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CMAKETYPES_H #define CMAKETYPES_H #include "cmakelistsparser.h" #include #include #include struct Macro { QString name; QStringList knownArgs; CMakeFileContent code; bool isFunction; }; struct CacheEntry { explicit CacheEntry(const QString& value=QString(), const QString &doc=QString()) : value(value), doc(doc) {} QString value; QString doc; }; struct Target { typedef QMap Properties; enum Type { Library, Executable, Custom }; KDevelop::IndexedDeclaration declaration; QStringList files; Type type; CMakeFunctionDesc desc; QString name; }; struct Subdirectory { QString name; CMakeFunctionDesc desc; QString build_dir; }; struct Test { Test() {} QString name; KDevelop::Path executable; QStringList arguments; QHash properties; }; Q_DECLARE_TYPEINFO(Test, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Subdirectory, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Target, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(CacheEntry, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(Macro, Q_MOVABLE_TYPE); enum PropertyType { GlobalProperty, DirectoryProperty, TargetProperty, SourceProperty, TestProperty, CacheProperty, VariableProperty }; typedef QHash > CategoryType; typedef QMap CMakeProperties; typedef QHash MacroMap; typedef QHash CMakeDefinitions; typedef QHash CacheValues; Q_DECLARE_METATYPE(QList) -Q_DECLARE_METATYPE(PropertyType); +Q_DECLARE_METATYPE(PropertyType) #endif diff --git a/plugins/custom-definesandincludes/compilerprovider/icompiler.h b/plugins/custom-definesandincludes/compilerprovider/icompiler.h index 450f6e69ad..cb8bb716cc 100644 --- a/plugins/custom-definesandincludes/compilerprovider/icompiler.h +++ b/plugins/custom-definesandincludes/compilerprovider/icompiler.h @@ -1,99 +1,99 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef ICOMPILER_H #define ICOMPILER_H #include #include #include "../idefinesandincludesmanager.h" namespace Utils { enum LanguageType { C, Cpp, OpenCl, Cuda, ObjC, Other }; } /// An interface that represents a compiler. Compiler provides standard include directories and standard defined macros. class ICompiler { public: /** * @param name The user visible name * @param path path to the compiler * @param factoryName name of the factory that created this compiler * @param editable whether user can change the name and the path to the compiler (should be set to false for automatically detected compilers) **/ ICompiler( const QString& name, const QString& path, const QString& factoryName, bool editable ); /** * @param arguments compiler command-line arguments * @return list of defined macros for the compiler */ virtual KDevelop::Defines defines(Utils::LanguageType type, const QString& arguments) const = 0; /** * @param arguments compiler command-line arguments * @return list of include directories for the compiler */ virtual KDevelop::Path::List includes(Utils::LanguageType type, const QString& arguments) const = 0; void setPath( const QString &path ); /// @return path to the compiler QString path() const; void setName( const QString &name ); /// @return user visible name QString name() const; /// Indicates if the compiler name/path can be set manually bool editable() const; /// @return name of the factory that created this compiler QString factoryName() const; virtual ~ICompiler() = default; private: bool m_editable; QString m_name; QString m_path; QString m_factoryName; }; typedef QSharedPointer CompilerPointer; -Q_DECLARE_METATYPE(CompilerPointer); +Q_DECLARE_METATYPE(CompilerPointer) #endif // ICOMPILER_H diff --git a/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h b/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h index 8f9a21479a..f2be54838a 100644 --- a/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h +++ b/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h @@ -1,112 +1,112 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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. * */ #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H #include "../idefinesandincludesmanager.h" #include "compilerprovider.h" #include "icompiler.h" #include class KConfig; namespace KDevelop { class IProject; class ProjectBaseItem; } struct ParserArguments { public: const QString& operator[](Utils::LanguageType languageType) const { Q_ASSERT(languageType >= Utils::C && languageType < Utils::Other); return arguments[languageType]; } QString& operator[](Utils::LanguageType languageType) { Q_ASSERT(languageType >= Utils::C && languageType < Utils::Other); return arguments[languageType]; } /// Is any of the arguments empty? bool isAnyEmpty() const; private: QString arguments[Utils::Other]; public: bool parseAmbiguousAsCPP; }; -Q_DECLARE_METATYPE(ParserArguments); +Q_DECLARE_METATYPE(ParserArguments) Q_DECLARE_TYPEINFO(ParserArguments, Q_MOVABLE_TYPE); struct ConfigEntry { QString path; QStringList includes; KDevelop::Defines defines; CompilerPointer compiler; ParserArguments parserArguments; explicit ConfigEntry( const QString& path = QString() ); // FIXME: get rid of this but stay backwards compatible void setDefines(const QHash& defines); }; Q_DECLARE_TYPEINFO(ConfigEntry, Q_MOVABLE_TYPE); namespace Utils { LanguageType languageType(const QString& path, bool treatAmbiguousAsCPP = true); } class SettingsManager { public: ~SettingsManager(); QVector readPaths(KConfig* cfg) const; void writePaths(KConfig* cfg, const QVector& paths); QVector userDefinedCompilers() const; void writeUserDefinedCompilers(const QVector& compilers); bool needToReparseCurrentProject( KConfig* cfg ) const; ParserArguments defaultParserArguments() const; CompilerProvider* provider(); const CompilerProvider* provider() const; static SettingsManager* globalInstance(); private: SettingsManager(); CompilerProvider m_provider; }; #endif // SETTINGSMANAGER_H diff --git a/plugins/customscript/customscript_plugin.cpp b/plugins/customscript/customscript_plugin.cpp index 6b8ce4c906..acf9f5af24 100644 --- a/plugins/customscript/customscript_plugin.cpp +++ b/plugins/customscript/customscript_plugin.cpp @@ -1,559 +1,559 @@ /* This file is part of KDevelop Copyright (C) 2008 Cédric Pasteur Copyright (C) 2011 David Nolden 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "customscript_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static QPointer indentPluginSingleton; K_PLUGIN_FACTORY_WITH_JSON(CustomScriptFactory, "kdevcustomscript.json", registerPlugin(); ) // Replaces ${KEY} in command with variables[KEY] static QString replaceVariables(QString command, QMap variables) { while (command.contains(QLatin1String("${"))) { int pos = command.indexOf(QLatin1String("${")); int end = command.indexOf(QLatin1String("}"), pos + 2); if (end == -1) { break; } QString key = command.mid(pos + 2, end - pos - 2); if (variables.contains(key)) { command.replace(pos, 1 + end - pos, variables[key]); } else { qCDebug(CUSTOMSCRIPT) << "found no variable while replacing in shell-command" << command << "key" << key << "available:" << variables; command.replace(pos, 1 + end - pos, QLatin1String("")); } } return command; } CustomScriptPlugin::CustomScriptPlugin(QObject* parent, const QVariantList&) : IPlugin(QStringLiteral("kdevcustomscript"), parent) { m_currentStyle = predefinedStyles().at(0); indentPluginSingleton = this; } CustomScriptPlugin::~CustomScriptPlugin() { } QString CustomScriptPlugin::name() const { // This needs to match the X-KDE-PluginInfo-Name entry from the .desktop file! return QStringLiteral("kdevcustomscript"); } QString CustomScriptPlugin::caption() const { return QStringLiteral("Custom Script Formatter"); } QString CustomScriptPlugin::description() const { return i18n("Indent and Format Source Code.
" "This plugin allows using powerful external formatting tools " "that can be invoked through the command-line.
" "For example, the uncrustify, astyle or indent " "formatters can be used.
" "The advantage of command-line formatters is that formatting configurations " "can be easily shared by all team members, independent of their preferred IDE."); } QString CustomScriptPlugin::formatSourceWithStyle(SourceFormatterStyle style, const QString& text, const QUrl& url, const QMimeType& /*mime*/, const QString& leftContext, const QString& rightContext) const { KProcess proc; QTextStream ios(&proc); std::unique_ptr tmpFile; if (style.content().isEmpty()) { style = predefinedStyle(style.name()); if (style.content().isEmpty()) { qCWarning(CUSTOMSCRIPT) << "Empty contents for style" << style.name() << "for indent plugin"; return text; } } QString useText = text; useText = leftContext + useText + rightContext; QMap projectVariables; foreach (IProject* project, ICore::self()->projectController()->projects()) { projectVariables[project->name()] = project->path().toUrl().toLocalFile(); } QString command = style.content(); // Replace ${Project} with the project path command = replaceVariables(command, projectVariables); command.replace(QLatin1String("$FILE"), url.toLocalFile()); if (command.contains(QLatin1String("$TMPFILE"))) { tmpFile.reset(new QTemporaryFile(QDir::tempPath() + "/code")); tmpFile->setAutoRemove(false); if (tmpFile->open()) { qCDebug(CUSTOMSCRIPT) << "using temporary file" << tmpFile->fileName(); command.replace(QLatin1String("$TMPFILE"), tmpFile->fileName()); QByteArray useTextArray = useText.toLocal8Bit(); if (tmpFile->write(useTextArray) != useTextArray.size()) { qCWarning(CUSTOMSCRIPT) << "failed to write text to temporary file"; return text; } } else { qCWarning(CUSTOMSCRIPT) << "Failed to create a temporary file"; return text; } tmpFile->close(); } qCDebug(CUSTOMSCRIPT) << "using shell command for indentation: " << command; proc.setShellCommand(command); proc.setOutputChannelMode(KProcess::OnlyStdoutChannel); proc.start(); if (!proc.waitForStarted()) { qCDebug(CUSTOMSCRIPT) << "Unable to start indent" << endl; return text; } if (!tmpFile.get()) { proc.write(useText.toLocal8Bit()); } proc.closeWriteChannel(); if (!proc.waitForFinished()) { qCDebug(CUSTOMSCRIPT) << "Process doesn't finish" << endl; return text; } QString output; if (tmpFile.get()) { QFile f(tmpFile->fileName()); if (f.open(QIODevice::ReadOnly)) { output = QString::fromLocal8Bit(f.readAll()); } else { qCWarning(CUSTOMSCRIPT) << "Failed opening the temporary file for reading"; return text; } } else { output = ios.readAll(); } if (output.isEmpty()) { qCWarning(CUSTOMSCRIPT) << "indent returned empty text for style" << style.name() << style.content(); return text; } int tabWidth = 4; if ((!leftContext.isEmpty() || !rightContext.isEmpty()) && (text.contains(' ') || output.contains(' '))) { // If we have to do contex-matching with tabs, determine the correct tab-width so that the context // can be matched correctly Indentation indent = indentation(url); if (indent.indentationTabWidth > 0) { tabWidth = indent.indentationTabWidth; } } return KDevelop::extractFormattedTextFromContext(output, text, leftContext, rightContext, tabWidth); } QString CustomScriptPlugin::formatSource(const QString& text, const QUrl& url, const QMimeType& mime, const QString& leftContext, const QString& rightContext) const { auto style = KDevelop::ICore::self()->sourceFormatterController()->styleForUrl(url, mime); return formatSourceWithStyle(style, text, url, mime, leftContext, rightContext); } static QVector stylesFromLanguagePlugins() { QVector styles; foreach (auto lang, ICore::self()->languageController()->loadedLanguages()) { const SourceFormatterItemList& languageStyles = lang->sourceFormatterItems(); for (const SourceFormatterStyleItem& item: languageStyles) { if (item.engine == QLatin1String("customscript")) { styles << item.style; } } } return styles; -}; +} KDevelop::SourceFormatterStyle CustomScriptPlugin::predefinedStyle(const QString& name) const { for (auto langStyle: stylesFromLanguagePlugins()) { qCDebug(CUSTOMSCRIPT) << "looking at style from language with custom sample" << langStyle.description() << langStyle.overrideSample(); if (langStyle.name() == name) { return langStyle; } } SourceFormatterStyle result(name); if (name == QLatin1String("GNU_indent_GNU")) { result.setCaption(i18n("Gnu Indent: GNU")); result.setContent(QStringLiteral("indent")); result.setUsePreview(true); } else if (name == QLatin1String("GNU_indent_KR")) { result.setCaption(i18n("Gnu Indent: Kernighan & Ritchie")); result.setContent(QStringLiteral("indent -kr")); result.setUsePreview(true); } else if (name == QLatin1String("GNU_indent_orig")) { result.setCaption(i18n("Gnu Indent: Original Berkeley indent style")); result.setContent(QStringLiteral("indent -orig")); result.setUsePreview(true); } else if (name == QLatin1String("kdev_format_source")) { result.setCaption(QStringLiteral("KDevelop: kdev_format_source")); result.setContent(QStringLiteral("kdev_format_source $FILE $TMPFILE")); result.setUsePreview(false); result.setDescription(i18n("Description:
" "kdev_format_source is a script bundled with KDevelop " "which allows using fine-grained formatting rules by placing " "meta-files called format_sources into the file-system.

" "Each line of the format_sources files defines a list of wildcards " "followed by a colon and the used formatting-command.

" "The formatting-command should use $TMPFILE to reference the " "temporary file to reformat.

" "Example:
" "*.cpp *.h : myformatter $TMPFILE
" "This will reformat all files ending with .cpp or .h using " "the custom formatting script myformatter.

" "Example:
" "subdir/* : uncrustify -l CPP -f $TMPFILE -c uncrustify.config -o $TMPFILE
" "This will reformat all files in subdirectory subdir using the uncrustify " "tool with the config-file uncrustify.config.")); } result.setMimeTypes({ {"text/x-c++src", "C++"}, {"text/x-chdr", "C"}, {"text/x-c++hdr", "C++"}, {"text/x-csrc", "C"}, {"text/x-java", "Java"}, {"text/x-csharp", "C#"} }); return result; } QVector CustomScriptPlugin::predefinedStyles() const { QVector styles = stylesFromLanguagePlugins(); styles << predefinedStyle(QStringLiteral("kdev_format_source")); styles << predefinedStyle(QStringLiteral("GNU_indent_GNU")); styles << predefinedStyle(QStringLiteral("GNU_indent_KR")); styles << predefinedStyle(QStringLiteral("GNU_indent_orig")); return styles; } KDevelop::SettingsWidget* CustomScriptPlugin::editStyleWidget(const QMimeType& mime) const { Q_UNUSED(mime); return new CustomScriptPreferences(); } static QString formattingSample() { return "// Formatting\n" "void func(){\n" "\tif(isFoo(a,b))\n" "\tbar(a,b);\n" "if(isFoo)\n" "\ta=bar((b-c)*a,*d--);\n" "if( isFoo( a,b ) )\n" "\tbar(a, b);\n" "if (isFoo) {isFoo=false;cat << isFoo <::const_iterator it = list.begin();\n" "}\n" "namespace A {\n" "namespace B {\n" "void foo() {\n" " if (true) {\n" " func();\n" " } else {\n" " // bla\n" " }\n" "}\n" "}\n" "}\n"; } static QString indentingSample() { return QLatin1String( "// Indentation\n" "#define foobar(A)\\\n" "{Foo();Bar();}\n" "#define anotherFoo(B)\\\n" "return Bar()\n" "\n" "namespace Bar\n" "{\n" "class Foo\n" "{public:\n" "Foo();\n" "virtual ~Foo();\n" "};\n" "void bar(int foo)\n" "{\n" "switch (foo)\n" "{\n" "case 1:\n" "a+=1;\n" "break;\n" "case 2:\n" "{\n" "a += 2;\n" " break;\n" "}\n" "}\n" "if (isFoo)\n" "{\n" "bar();\n" "}\n" "else\n" "{\n" "anotherBar();\n" "}\n" "}\n" "int foo()\n" "\twhile(isFoo)\n" "\t\t{\n" "\t\t\t// ...\n" "\t\t\tgoto error;\n" "\t\t/* .... */\n" "\t\terror:\n" "\t\t\t//...\n" "\t\t}\n" "\t}\n" "fooArray[]={ red,\n" "\tgreen,\n" "\tdarkblue};\n" "fooFunction(barArg1,\n" "\tbarArg2,\n" "\tbarArg3);\n" ); } QString CustomScriptPlugin::previewText(const SourceFormatterStyle& style, const QMimeType& /*mime*/) const { if (!style.overrideSample().isEmpty()) { return style.overrideSample(); } return formattingSample() + "\n\n" + indentingSample(); } QStringList CustomScriptPlugin::computeIndentationFromSample(const QUrl& url) const { QStringList ret; auto languages = ICore::self()->languageController()->languagesForUrl(url); if (languages.isEmpty()) { return ret; } QString sample = languages[0]->indentationSample(); QString formattedSample = formatSource(sample, url, QMimeDatabase().mimeTypeForUrl(url), QString(), QString()); QStringList lines = formattedSample.split(QStringLiteral("\n")); foreach (QString line, lines) { if (!line.isEmpty() && line[0].isSpace()) { QString indent; foreach (QChar c, line) { if (c.isSpace()) { indent.push_back(c); } else { break; } } if (!indent.isEmpty() && !ret.contains(indent)) { ret.push_back(indent); } } } return ret; } CustomScriptPlugin::Indentation CustomScriptPlugin::indentation(const QUrl& url) const { Indentation ret; QStringList indent = computeIndentationFromSample(url); if (indent.isEmpty()) { qCDebug(CUSTOMSCRIPT) << "failed extracting a valid indentation from sample for url" << url; return ret; // No valid indentation could be extracted } if (indent[0].contains(' ')) { ret.indentWidth = indent[0].count(' '); } if (!indent.join(QLatin1String("")).contains(' ')) { ret.indentationTabWidth = -1; // Tabs are not used for indentation } if (indent[0] == QLatin1String(" ")) { // The script indents with tabs-only // The problem is that we don't know how // wide a tab is supposed to be. // // We need indentation-width=tab-width // to make the editor do tab-only formatting, // so choose a random with of 4. ret.indentWidth = 4; ret.indentationTabWidth = 4; } else if (ret.indentWidth) { // Tabs are used for indentation, alongside with spaces // Try finding out how many spaces one tab stands for. // Do it by assuming a uniform indentation-step with each level. for (int pos = 0; pos < indent.size(); ++pos) { if (indent[pos] == QLatin1String(" ")&& pos >= 1) { // This line consists of only a tab. int prevWidth = indent[pos - 1].length(); int prevPrevWidth = (pos >= 2) ? indent[pos - 2].length() : 0; int step = prevWidth - prevPrevWidth; qCDebug(CUSTOMSCRIPT) << "found in line " << pos << prevWidth << prevPrevWidth << step; if (step > 0 && step <= prevWidth) { qCDebug(CUSTOMSCRIPT) << "Done"; ret.indentationTabWidth = prevWidth + step; break; } } } } qCDebug(CUSTOMSCRIPT) << "indent-sample" << "\"" + indent.join(QStringLiteral("\n")) + "\"" << "extracted tab-width" << ret.indentationTabWidth << "extracted indentation width" << ret.indentWidth; return ret; } void CustomScriptPreferences::updateTimeout() { const QString& text = indentPluginSingleton.data()->previewText(m_style, QMimeType()); QString formatted = indentPluginSingleton.data()->formatSourceWithStyle(m_style, text, QUrl(), QMimeType()); emit previewTextChanged(formatted); } CustomScriptPreferences::CustomScriptPreferences() { m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect(m_updateTimer, &QTimer::timeout, this, &CustomScriptPreferences::updateTimeout); m_vLayout = new QVBoxLayout(this); m_vLayout->setMargin(0); m_captionLabel = new QLabel; m_vLayout->addWidget(m_captionLabel); m_vLayout->addSpacing(10); m_hLayout = new QHBoxLayout; m_vLayout->addLayout(m_hLayout); m_commandLabel = new QLabel; m_hLayout->addWidget(m_commandLabel); m_commandEdit = new QLineEdit; m_hLayout->addWidget(m_commandEdit); m_commandLabel->setText(i18n("Command:")); m_vLayout->addSpacing(10); m_bottomLabel = new QLabel; m_vLayout->addWidget(m_bottomLabel); m_bottomLabel->setTextFormat(Qt::RichText); m_bottomLabel->setText( i18n("You can enter an arbitrary shell command.
" "The unformatted source-code is reached to the command
" "through the standard input, and the
" "formatted result is read from the standard output.
" "
" "If you add $TMPFILE into the command, then
" "a temporary file is used for transferring the code.")); connect(m_commandEdit, &QLineEdit::textEdited, this, &CustomScriptPreferences::textEdited); m_vLayout->addSpacing(10); m_moreVariablesButton = new QPushButton(i18n("More Variables")); connect(m_moreVariablesButton, &QPushButton::clicked, this, &CustomScriptPreferences::moreVariablesClicked); m_vLayout->addWidget(m_moreVariablesButton); m_vLayout->addStretch(); } void CustomScriptPreferences::load(const KDevelop::SourceFormatterStyle& style) { m_style = style; m_commandEdit->setText(style.content()); m_captionLabel->setText(i18n("Style: %1", style.caption())); updateTimeout(); } QString CustomScriptPreferences::save() { return m_commandEdit->text(); } void CustomScriptPreferences::moreVariablesClicked(bool) { KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("$TMPFILE will be replaced with the path to a temporary file.
" "The code will be written into the file, the temporary
" "file will be substituted into that position, and the result
" "will be read out of that file.
" "
" "$FILE will be replaced with the path of the original file.
" "The contents of the file must not be modified, changes are allowed
" "only in $TMPFILE.
" "
" "${PROJECT_NAME} will be replaced by the path of
" "the currently open project with the matching name." ), i18n("Variable Replacements")); } #include "customscript_plugin.moc" // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/plugins/executeplasmoid/plasmoidexecutionconfig.cpp b/plugins/executeplasmoid/plasmoidexecutionconfig.cpp index 25e4b724e9..c63a2d5aa3 100644 --- a/plugins/executeplasmoid/plasmoidexecutionconfig.cpp +++ b/plugins/executeplasmoid/plasmoidexecutionconfig.cpp @@ -1,329 +1,329 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plasmoidexecutionconfig.h" #include "plasmoidexecutionjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class la; -Q_DECLARE_METATYPE(KDevelop::IProject*); +Q_DECLARE_METATYPE(KDevelop::IProject*) QIcon PlasmoidExecutionConfig::icon() const { return QIcon::fromTheme("system-run"); } QStringList readProcess(QProcess* p) { QStringList ret; while(!p->atEnd()) { QByteArray line = p->readLine(); int nameEnd=line.indexOf(' '); if(nameEnd>0) { ret += line.left(nameEnd); } } return ret; } PlasmoidExecutionConfig::PlasmoidExecutionConfig( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); connect( identifier->lineEdit(), &QLineEdit::textEdited, this, &PlasmoidExecutionConfig::changed ); QProcess pPlasmoids; pPlasmoids.start(QStringLiteral("plasmoidviewer"), QStringList(QStringLiteral("--list")), QIODevice::ReadOnly); QProcess pThemes; pThemes.start(QStringLiteral("plasmoidviewer"), QStringList(QStringLiteral("--list-themes")), QIODevice::ReadOnly); pThemes.waitForFinished(); pPlasmoids.waitForFinished(); foreach(const QString& plasmoid, readProcess(&pPlasmoids)) { identifier->addItem(plasmoid); } themes->addItem(QString()); foreach(const QString& theme, readProcess(&pThemes)) { themes->addItem(theme); } connect( dependencies, &KDevelop::DependenciesWidget::changed, this, &PlasmoidExecutionConfig::changed ); } void PlasmoidExecutionConfig::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry("PlasmoidIdentifier", identifier->lineEdit()->text()); QStringList args; args += QStringLiteral("--formfactor"); args += formFactor->currentText(); if(!themes->currentText().isEmpty()) { args += QStringLiteral("--theme"); args += themes->currentText(); } cfg.writeEntry("Arguments", args); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( "Dependencies", KDevelop::qvariantToString( QVariant( deps ) ) ); } void PlasmoidExecutionConfig::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* ) { bool b = blockSignals( true ); identifier->lineEdit()->setText(cfg.readEntry("PlasmoidIdentifier", "")); blockSignals( b ); QStringList arguments = cfg.readEntry("Arguments", QStringList()); int idxFormFactor = arguments.indexOf(QStringLiteral("--formfactor"))+1; if(idxFormFactor>0) formFactor->setCurrentIndex(formFactor->findText(arguments[idxFormFactor])); int idxTheme = arguments.indexOf(QStringLiteral("--theme"))+1; if(idxTheme>0) themes->setCurrentIndex(themes->findText(arguments[idxTheme])); dependencies->setDependencies( KDevelop::stringToQVariant( cfg.readEntry( "Dependencies", QString() ) ).toList()); } QString PlasmoidExecutionConfig::title() const { return i18n("Configure Plasmoid Execution"); } QList< KDevelop::LaunchConfigurationPageFactory* > PlasmoidLauncher::configPages() const { return QList(); } QString PlasmoidLauncher::description() const { return i18n("Display a plasmoid"); } QString PlasmoidLauncher::id() { return QStringLiteral("PlasmoidLauncher"); } QString PlasmoidLauncher::name() const { return i18n("Plasmoid Launcher"); } PlasmoidLauncher::PlasmoidLauncher(ExecutePlasmoidPlugin* plugin) : m_plugin(plugin) { } KJob* PlasmoidLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return nullptr; } if( launchMode == QLatin1String("execute") ) { KJob* depsJob = dependencies(cfg); QList jobs; if(depsJob) jobs << depsJob; jobs << new PlasmoidExecutionJob(m_plugin, cfg); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), jobs ); } qCWarning(EXECUTEPLASMOID) << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return nullptr; } KJob* PlasmoidLauncher::calculateDependencies(KDevelop::ILaunchConfiguration* cfg) { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( "Dependencies", QString() ) ).toList(); if( !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Could not resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob; job->addItems( KDevelop::BuilderJob::Install, items ); job->updateJobName(); return job; } return nullptr; } KJob* PlasmoidLauncher::dependencies(KDevelop::ILaunchConfiguration* cfg) { return calculateDependencies(cfg); } QStringList PlasmoidLauncher::supportedModes() const { return QStringList() << QStringLiteral("execute"); } KDevelop::LaunchConfigurationPage* PlasmoidPageFactory::createWidget(QWidget* parent) { return new PlasmoidExecutionConfig( parent ); } PlasmoidPageFactory::PlasmoidPageFactory() {} PlasmoidExecutionConfigType::PlasmoidExecutionConfigType() { factoryList.append( new PlasmoidPageFactory ); } PlasmoidExecutionConfigType::~PlasmoidExecutionConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString PlasmoidExecutionConfigType::name() const { return i18n("Plasmoid Launcher"); } QList PlasmoidExecutionConfigType::configPages() const { return factoryList; } QString PlasmoidExecutionConfigType::typeId() { return QStringLiteral("PlasmoidLauncherType"); } QIcon PlasmoidExecutionConfigType::icon() const { return QIcon::fromTheme("plasma"); } static bool canLaunchMetadataFile(const KDevelop::Path &path) { KConfig cfg(path.toLocalFile(), KConfig::SimpleConfig); KConfigGroup group(&cfg, "Desktop Entry"); QStringList services = group.readEntry("ServiceTypes", group.readEntry("X-KDE-ServiceTypes", QStringList())); return services.contains(QStringLiteral("Plasma/Applet")); } //don't bother, nobody uses this interface bool PlasmoidExecutionConfigType::canLaunch(const QUrl& ) const { return false; } bool PlasmoidExecutionConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { KDevelop::ProjectFolderItem* folder = item->folder(); if(folder && folder->hasFileOrFolder(QStringLiteral("metadata.desktop"))) { return canLaunchMetadataFile(KDevelop::Path(folder->path(), QStringLiteral("metadata.desktop"))); } return false; } void PlasmoidExecutionConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry("PlasmoidIdentifier", item->path().toUrl().toLocalFile()); } void PlasmoidExecutionConfigType::configureLaunchFromCmdLineArguments(KConfigGroup /*config*/, const QStringList &/*args*/) const {} QMenu* PlasmoidExecutionConfigType::launcherSuggestions() { QList found; QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* p, projects) { QSet files = p->fileSet(); foreach(const KDevelop::IndexedString& file, files) { KDevelop::Path path(file.str()); if (path.lastPathSegment() == QLatin1String("metadata.desktop") && canLaunchMetadataFile(path)) { path = path.parent(); QString relUrl = p->path().relativePath(path); QAction* action = new QAction(relUrl, this); action->setProperty("url", relUrl); action->setProperty("project", qVariantFromValue(p)); connect(action, &QAction::triggered, this, &PlasmoidExecutionConfigType::suggestionTriggered); found.append(action); } } } QMenu *m = nullptr; if(!found.isEmpty()) { m = new QMenu(i18n("Plasmoids")); m->addActions(found); } return m; } void PlasmoidExecutionConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::IProject* p = action->property("project").value(); QString relUrl = action->property("url").toString(); KDevelop::ILauncher* launcherInstance = launchers().at( 0 ); QPair launcher = qMakePair( launcherInstance->supportedModes().at(0), launcherInstance->id() ); QString name = relUrl.mid(relUrl.lastIndexOf('/')+1); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, name); KConfigGroup cfg = config->config(); cfg.writeEntry("PlasmoidIdentifier", relUrl); emit signalAddLaunchConfiguration(config); } diff --git a/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.cpp b/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.cpp index 4aff922bdd..28b6b29cb0 100644 --- a/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.cpp +++ b/plugins/filetemplates/tests/data/testgenerationtest/templates/test_qtestlib/class.cpp @@ -1,45 +1,45 @@ {% load kdev_filters %} /* {{ license|lines_prepend:" * " }} */ #include "{{ output_file_header }}" void {{ name }}::initTestCase() { // Called before the first testfunction is executed } void {{ name }}::cleanupTestCase() { // Called after the last testfunction was executed } void {{ name }}::init() { // Called before each testfunction is executed } void {{ name }}::cleanup() { // Called after every testfunction } {% for case in testCases %} void {{ name }}::{{ case }}() { } {% endfor %} -QTEST_MAIN({{ name }}); +QTEST_MAIN({{ name }}) #include "{{ output_file_header|cut:".h" }}.moc" \ No newline at end of file diff --git a/plugins/filetemplates/tests/expected/testname.cpp b/plugins/filetemplates/tests/expected/testname.cpp index 3f635bcb61..e0293e0bf9 100644 --- a/plugins/filetemplates/tests/expected/testname.cpp +++ b/plugins/filetemplates/tests/expected/testname.cpp @@ -1,43 +1,43 @@ /* * Test license header * In two lines */ #include "testname.h" void TestName::initTestCase() { // Called before the first testfunction is executed } void TestName::cleanupTestCase() { // Called after the last testfunction was executed } void TestName::init() { // Called before each testfunction is executed } void TestName::cleanup() { // Called after every testfunction } void TestName::firstTestCase() { } void TestName::secondTestCase() { } void TestName::thirdTestCase() { } -QTEST_MAIN(TestName); +QTEST_MAIN(TestName) #include "testname.moc" diff --git a/plugins/filetemplates/tests/test_generationtest.cpp b/plugins/filetemplates/tests/test_generationtest.cpp index c06e165ee7..4c12060276 100644 --- a/plugins/filetemplates/tests/test_generationtest.cpp +++ b/plugins/filetemplates/tests/test_generationtest.cpp @@ -1,130 +1,130 @@ /* * This file is part of KDevelop * * 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 "test_generationtest.h" #include "tests_config.h" #include #include #include #include #include #include #include #include using namespace KDevelop; #define COMPARE_FILES(name) \ do { \ QFile actualFile(Path(Path(baseUrl), QStringLiteral(name)).toLocalFile()); \ QVERIFY(actualFile.open(QIODevice::ReadOnly)); \ QFile expectedFile(QStringLiteral(TESTS_EXPECTED_DIR "/" name)); \ QVERIFY(expectedFile.open(QIODevice::ReadOnly)); \ QCOMPARE(actualFile.size(), expectedFile.size()); \ QCOMPARE(QString(actualFile.readAll()), QString(expectedFile.readAll())); \ } while(0) void TestGenerationTest::initTestCase() { QByteArray xdgData = qgetenv("XDG_DATA_DIRS"); xdgData.prepend(TESTS_DATA_DIR ":"); bool addedDir = qputenv("XDG_DATA_DIRS", xdgData); QVERIFY(addedDir); // avoid translated desktop entries, tests use untranslated strings QLocale::setDefault(QLocale::c()); AutoTestShell::init(); TestCore::initialize (Core::NoUi); TemplatesModel model(QStringLiteral("testgenerationtest")); model.refresh(); renderer = new TemplateRenderer; renderer->setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); renderer->addVariable(QStringLiteral("name"), "TestName"); renderer->addVariable(QStringLiteral("license"), "Test license header\nIn two lines"); QStringList testCases; testCases << QStringLiteral("firstTestCase"); testCases << QStringLiteral("secondTestCase"); testCases << QStringLiteral("thirdTestCase"); renderer->addVariable(QStringLiteral("testCases"), testCases); } void TestGenerationTest::cleanupTestCase() { delete renderer; TestCore::shutdown(); } void TestGenerationTest::init() { dir.reset(new QTemporaryDir); baseUrl = QUrl::fromLocalFile(dir->path()); } void TestGenerationTest::yamlTemplate() { QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_yaml2.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing YAML Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.yaml"); } void TestGenerationTest::cppTemplate() { QString description = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("testgenerationtest/template_descriptions/test_qtestlib.desktop")); QVERIFY(!description.isEmpty()); SourceFileTemplate file; file.addAdditionalSearchLocation(QStringLiteral(TESTS_DATA_DIR "/testgenerationtest/templates")); file.setTemplateDescription(description); QCOMPARE(file.name(), QStringLiteral("Testing C++ Template")); DocumentChangeSet changes = renderer->renderFileTemplate(file, baseUrl, urls(file)); changes.applyAllChanges(); COMPARE_FILES("testname.h"); COMPARE_FILES("testname.cpp"); } QHash< QString, QUrl > TestGenerationTest::urls (const SourceFileTemplate& file) { QHash ret; foreach (const SourceFileTemplate::OutputFile& output, file.outputFiles()) { QUrl url = Path(Path(baseUrl), renderer->render(output.outputName).toLower()).toUrl(); ret.insert(output.identifier, url); } return ret; } -QTEST_GUILESS_MAIN(TestGenerationTest); +QTEST_GUILESS_MAIN(TestGenerationTest) diff --git a/plugins/grepview/tests/test_findreplace.cpp b/plugins/grepview/tests/test_findreplace.cpp index 7c5d91ccd7..334fccf235 100644 --- a/plugins/grepview/tests/test_findreplace.cpp +++ b/plugins/grepview/tests/test_findreplace.cpp @@ -1,200 +1,200 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include #include #include #include #include "test_findreplace.h" #include "../grepjob.h" #include "../grepviewplugin.h" #include "../grepoutputmodel.h" void FindReplaceTest::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); } void FindReplaceTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void FindReplaceTest::testFind_data() { QTest::addColumn("subject"); QTest::addColumn("search"); QTest::addColumn("matches"); QTest::newRow("Basic") << "foobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3)); QTest::newRow("Multiple matches") << "foobar\nbar\nbarfoo" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(2, 3, 6)); QTest::newRow("Multiple on same line") << "foobarbaz" << QRegExp("ba") << (MatchList() << Match(0, 3, 5) << Match(0, 6, 8)); QTest::newRow("Multiple sticked together") << "foofoobar" << QRegExp("foo") << (MatchList() << Match(0, 0, 3) << Match(0, 3, 6)); QTest::newRow("RegExp (member call)") << "foo->bar ();\nbar();" << QRegExp("\\->\\s*\\b(bar)\\b\\s*\\(") << (MatchList() << Match(0, 3, 10)); // the matching must be started after the last previous match QTest::newRow("RegExp (greedy match)") << "foofooo" << QRegExp("[o]+") << (MatchList() << Match(0, 1, 3) << Match(0, 4, 7)); QTest::newRow("Matching EOL") << "foobar\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Matching EOL (Windows style)") << "foobar\r\nfoobar" << QRegExp("foo.*") << (MatchList() << Match(0, 0, 6) << Match(1, 0, 6)); QTest::newRow("Empty lines handling") << "foo\n\n\n" << QRegExp("bar") << (MatchList()); QTest::newRow("Can match empty string (at EOL)") << "foobar\n" << QRegExp(".*") << (MatchList() << Match(0, 0, 6)); QTest::newRow("Matching empty string anywhere") << "foobar\n" << QRegExp("") << (MatchList()); } void FindReplaceTest::testFind() { QFETCH(QString, subject); QFETCH(QRegExp, search); QFETCH(MatchList, matches); QTemporaryFile file; QVERIFY(file.open()); file.write(subject.toUtf8()); file.close(); GrepOutputItem::List actualMatches = grepFile(file.fileName(), search); QCOMPARE(actualMatches.length(), matches.length()); for(int i=0; im_range.start().line(), matches[i].line); QCOMPARE(actualMatches[i].change()->m_range.start().column(), matches[i].start); QCOMPARE(actualMatches[i].change()->m_range.end().column(), matches[i].end); } // check that file has not been altered by grepFile QVERIFY(file.open()); QCOMPARE(QString(file.readAll()), subject); } void FindReplaceTest::testReplace_data() { QTest::addColumn("subject"); QTest::addColumn("searchPattern"); QTest::addColumn("searchTemplate"); QTest::addColumn("replace"); QTest::addColumn("replaceTemplate"); QTest::addColumn("result"); QTest::newRow("Raw replace") << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\nreplacement\nsome other test\n")) << File(QStringLiteral("otherfile.txt"), QStringLiteral("some replacement text\n\n"))) << "replacement" << "%s" << "dummy" << "%s" << (FileList() << File(QStringLiteral("myfile.txt"), QStringLiteral("some text\ndummy\nsome other test\n")) << File(QStringLiteral("otherfile.txt"), QStringLiteral("some dummy text\n\n"))); // see bug: https://bugs.kde.org/show_bug.cgi?id=301362 QTest::newRow("LF character replace") << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n"))) << "\\\\n" << "%s" << "\\n\\n" << "%s" << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("hello world\\n\\n"))); QTest::newRow("Template replace") << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setFoo(0);\n setFoo(0); /*not replaced*/"))) << "setFoo" << "\\->\\s*\\b%s\\b\\s*\\(" << "setBar" << "->%s(" << (FileList() << File(QStringLiteral("somefile.h"), QStringLiteral("struct Foo {\n void setFoo(int foo);\n};")) << File(QStringLiteral("somefile.cpp"), QStringLiteral("instance->setBar(0);\n setFoo(0); /*not replaced*/"))); QTest::newRow("Template with captures") << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::func(1, 2)\n otherInst :: func (\"foo\")\n func()"))) << "func" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "REPL" << "\\1::%s(" << (FileList() << File(QStringLiteral("somefile.cpp"), QStringLiteral("inst::REPL(1, 2)\n otherInst::REPL(\"foo\")\n func()"))); QTest::newRow("Regexp pattern") << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("foobar\n foooobar\n fake"))) << "f\\w*o" << "%s" << "FOO" << "%s" << (FileList() << File(QStringLiteral("somefile.txt"), QStringLiteral("FOObar\n FOObar\n fake"))); } void FindReplaceTest::testReplace() { QFETCH(FileList, subject); QFETCH(QString, searchPattern); QFETCH(QString, searchTemplate); QFETCH(QString, replace); QFETCH(QString, replaceTemplate); QFETCH(FileList, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); // we need some convenience functions that are not in QTemporaryDir foreach(const File& fileData, subject) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::WriteOnly)); QVERIFY(file.write(fileData.second.toUtf8()) != -1); file.close(); } GrepJob *job = new GrepJob(this); GrepOutputModel *model = new GrepOutputModel(job); GrepJobSettings settings; job->setOutputModel(model); job->setDirectoryChoice(QList() << QUrl::fromLocalFile(dir.path())); settings.projectFilesOnly = false; settings.caseSensitive = true; settings.regexp = true; settings.depth = -1; // fully recursive settings.pattern = searchPattern; settings.searchTemplate = searchTemplate; settings.replacementTemplate = replaceTemplate; settings.files = QStringLiteral("*"); settings.exclude = QString(); job->setSettings(settings); QVERIFY(job->exec()); QVERIFY(model->hasResults()); model->setReplacement(replace); model->makeItemsCheckable(true); model->doReplacements(); foreach(const File& fileData, result) { QFile file(dir.filePath(fileData.first)); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(QString(file.readAll()), fileData.second); file.close(); } tempDir.remove(); } -QTEST_MAIN(FindReplaceTest); +QTEST_MAIN(FindReplaceTest) diff --git a/plugins/lldb/unittests/test_lldb.cpp b/plugins/lldb/unittests/test_lldb.cpp index fcdf4e1de1..782a28d1c9 100644 --- a/plugins/lldb/unittests/test_lldb.cpp +++ b/plugins/lldb/unittests/test_lldb.cpp @@ -1,1903 +1,1903 @@ /* * Unit tests for LLDB debugger plugin Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov * Copyright 2016 Aetf * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_lldb.h" #include "controllers/framestackmodel.h" #include "debugsession.h" #include "tests/debuggers-tests-config.h" #include "tests/testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR_A_WHILE(session, ms) \ do { if (!KDevMI::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ KDevMI::TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (KDevMI::isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) using namespace KDevelop; using namespace KDevMI::LLDB; using KDevMI::findExecutable; using KDevMI::findFile; using KDevMI::findSourceFile; namespace { class WritableEnvironmentProfileList : public EnvironmentProfileList { public: explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} using EnvironmentProfileList::variables; using EnvironmentProfileList::saveSettings; using EnvironmentProfileList::removeProfile; }; class TestLaunchConfiguration : public ILaunchConfiguration { public: explicit TestLaunchConfiguration(const QUrl& executable = findExecutable(QStringLiteral("debuggee_debugee")), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QStringLiteral("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public LldbFrameStackModel { public: explicit TestFrameStackModel(DebugSession* session) : LldbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; LldbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; LldbFrameStackModel::fetchThreads(); } int fetchFramesCalled; int fetchThreadsCalled; }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { // explicit set formatter path to force use in-tree formatters, not the one installed in system. auto formatter = findFile(LLDB_SRC_DIR, "formatters/all.py"); setFormatterPath(formatter); setSourceInitFile(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; } // end of anonymous namespace BreakpointModel* LldbTest::breakpoints() { return m_core->debugController()->breakpointModel(); } VariableCollection *LldbTest::variableCollection() { return m_core->debugController()->variableCollection(); } Variable *LldbTest::watchVariableAt(int i) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); auto idx = variableCollection()->index(i, 0, watchRoot); return dynamic_cast(variableCollection()->itemForIndex(idx)); } QModelIndex LldbTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } // Called before the first testfunction is executed void LldbTest::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); m_iface = m_core->pluginController() ->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute")) ->extension(); Q_ASSERT(m_iface); m_debugeeFileName = findSourceFile("debugee.cpp"); const QString lldbMiExecutable = QStandardPaths::findExecutable(QStringLiteral("lldb-mi")); if (lldbMiExecutable.isEmpty()) { QSKIP("Skipping, lldb-mi not available"); } } // Called after the last testfunction was executed void LldbTest::cleanupTestCase() { TestCore::shutdown(); } // Called before each testfunction is executed void LldbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); bpCfg.writeEntry("number", 0); bpCfg.sync(); breakpoints()->removeRows(0, breakpoints()->rowCount()); while (variableCollection()->watches()->childCount() > 0) { auto var = watchVariableAt(0); if (!var) break; var->die(); } } void LldbTest::cleanup() { // Called after every testfunction } void LldbTest::testStdout() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "Hello, world!" << "Hello"); } void LldbTest::testEnvironmentSet() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeechoenv"))); cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); envProfiles.removeProfile(QStringLiteral("LldbTestGroup")); auto &envs = envProfiles.variables(QStringLiteral("LldbTestGroup")); envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); envProfiles.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void LldbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); QCOMPARE(session->currentLine(), 29); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void LldbTest::testBreakOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); // line 28 is the start of main function in debugee.cpp QCOMPARE(session->currentLine(), 27); // currentLine is zero-based session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDisableBreakpoint() { QSKIP("Skipping... In lldb-mi -d flag has no effect when mixed with -f"); //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. m_core->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added auto *thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), thirdBreakLine); m_core->debugController()->breakpointModel()->blockSignals(false); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), fourthBreakLine); WAIT_FOR_A_WHILE(session, 300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); WAIT_FOR_A_WHILE(session, 300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 27); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); WAIT_FOR_A_WHILE(session, 100); b->setLine(28); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 28); WAIT_FOR_A_WHILE(session, 500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(m_debugeeFileName+":30")); QCOMPARE(b->line(), 29); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(b->line(), 29); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPendingBreakpoint() { QSKIP("Skipping... Pending breakpoint not work on lldb-mi"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); auto * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_lldb.cpp")), 10); QCOMPARE(b->state(), Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testUpdateBreakpoint() { // Description: user might insert breakpoints using lldb console. model should // pick up the manually set breakpoint TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // break at line 29 auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 29 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(session->currentLine(), 23-1); // at the beginning of foo():23: ++i; session->addUserCommand(QStringLiteral("break set --file %1 --line %2").arg(m_debugeeFileName).arg(33)); WAIT_FOR_A_WHILE(session, 20); QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(b->line(), 33-1); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 25 QCOMPARE(session->currentLine(), 33-1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testIgnoreHitsBreakpoint() { QSKIP("Skipping... lldb-mi doesn't provide breakpoint hit count update"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 39); b->setCondition(QStringLiteral("x[0] == 'H'")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition(QStringLiteral("i==2")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->currentLine() == 24); b->setCondition(QStringLiteral("i == 0")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addWatchpoint(QStringLiteral("i")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteWithConditionBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint(QStringLiteral("i")); b->setCondition(QStringLiteral("i==2")); QTest::qWait(100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addReadWatchpoint(QStringLiteral("foo::i")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint2() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addReadWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnAccessBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addAccessWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; auto b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); auto b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 23")); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 1); auto b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, QStringLiteral("break disable 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -c 'i == 1' 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -i 1 2")); WAIT_FOR_A_WHILE(session, 1000); QCOMPARE(b->enabled(), false); QEXPECT_FAIL("", "LLDB 4.0 does not report condition in mi response", Continue); QCOMPARE(b->condition(), QString("i == 1")); QEXPECT_FAIL("", "LLDB 4.0 does not report ignore hits in mi response", Continue); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, QStringLiteral("break delete 2")); WAIT_FOR_A_WHILE(session, 100); QEXPECT_FAIL("", "LLDB 4.0 does not report breakpoint deletion as mi notification", Continue); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 201771 void LldbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 1000); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 200); // wait for feedback notification from lldb-mi b->setDeleted(); WAIT_FOR_A_WHILE(session, 3000); // give slow debugee extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 32")); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void LldbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; QString sourceFile = findSourceFile("debugee.cpp"); //inject here, so it behaves similar like a command from .lldbinit QTemporaryFile configScript; configScript.open(); configScript.write(QStringLiteral("break set --file %0 --line 32\n").arg(sourceFile).toLocal8Bit()); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(QStringLiteral("debugee.cpp")), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeespace"))); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QEXPECT_FAIL("", "See LLDB bug 28703: -d flag has no effect", Abort); QCOMPARE(session->currentLine(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 34); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("aPlusB")); //TODO check if the additional location breakpoint is added QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 19); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultiplebreakpoint"))); auto b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeemultiplebreakpoint.cpp:52")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRegularExpressionBreakpoint() { QSKIP("Skipping... lldb has only one breakpoint for multiple locations" " (and lldb-mi returns the first one), not support this yet"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --func-regex .*aPl.*B")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, QLatin1String("")); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeBreakpointWhileRunning() { QSKIP("Skipping... lldb-mi command -break-enable doesn't enable breakpoint"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debuggee_debugeeslow"))); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeeslow.cpp:25")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Disabling breakpoint"; b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop WAIT_FOR_A_WHILE(session, 2500); qDebug() << "Waiting for active"; WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Enabling breakpoint"; // Use native user command works, but not through -break-enable, which is triggered by setData session->addCommand(MI::NonMI, QStringLiteral("break enable")); //b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeexception"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel* fsModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->currentLine(), 29); session->addCommand(MI::NonMI, QStringLiteral("break set -E c++")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); const auto frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().toUrl(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void LldbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 4); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":23"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(1, 2, tIdx), m_debugeeFileName+":29"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(3, 0, tIdx), "3"); COMPARE_DATA(stackModel->index(3, 1, tIdx), "_start"); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeerecursion"))); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":26"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(1, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(2, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(19, 0, tIdx), "19"); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); COMPARE_DATA(stackModel->index(21, 0, tIdx), "21"); COMPARE_DATA(stackModel->index(22, 0, tIdx), "22"); COMPARE_DATA(stackModel->index(39, 0, tIdx), "39"); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); COMPARE_DATA(stackModel->index(41, 0, tIdx), "41"); COMPARE_DATA(stackModel->index(42, 0, tIdx), "42"); COMPARE_DATA(stackModel->index(119, 0, tIdx), "119"); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); COMPARE_DATA(stackModel->index(121, 0, tIdx), "121"); COMPARE_DATA(stackModel->index(122, 0, tIdx), "122"); COMPARE_DATA(stackModel->index(298, 0, tIdx), "298"); COMPARE_DATA(stackModel->index(298, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(298, 2, tIdx), fileName+":30"); COMPARE_DATA(stackModel->index(299, 0, tIdx), "299"); COMPARE_DATA(stackModel->index(299, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(300, 0, tIdx), "300"); COMPARE_DATA(stackModel->index(300, 1, tIdx), "_start"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackSwitchThread() { QSKIP("Skipping... lldb-mi crashes when break at a location with multiple threads running"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeethreads"))); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); WAIT_FOR_A_WHILE(session, 200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debuggee_debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_A_WHILE(session, 100); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 35); // lldb-mi sliently stops when attaching to a process. Force it continue to run. session->addCommand(MI::ExecContinue, QString(), MI::CmdMaybeStartsRunning); WAIT_FOR_A_WHILE(session, 2000); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRemoteDebugging() { KProcess gdbServer; gdbServer << QStringLiteral("lldb-server") << QStringLiteral("gdbserver") << QStringLiteral("*:1234"); gdbServer.start(); QVERIFY(gdbServer.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::LldbRemoteDebuggingEntry, true); cfg.config().writeEntry(Config::LldbRemoteServerEntry, "localhost:1234"); cfg.config().writeEntry(Config::LldbRemotePathEntry, "/tmp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCoreFile() { QFileInfo f(QStringLiteral("core")); f.setCaching(false); // don't cache information if (f.exists()) { QVERIFY(QFile::remove(f.canonicalFilePath())); } KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << QStringLiteral("bash") << QStringLiteral("-c") << "ulimit -c unlimited; " + findExecutable(QStringLiteral("debuggee_crash")).toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = f.exists(); if (!coreFileFound) { // Try to use coredumpctl qDebug() << "try to use coredumpctl"; auto coredumpctl = QStandardPaths::findExecutable(QStringLiteral("coredumpctl")); if (!coredumpctl.isEmpty()) { KProcess::execute(coredumpctl, {"-1", "-o", f.absoluteFilePath(), "dump", "debuggee_crash"}); coreFileFound = f.exists(); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable(QStringLiteral("debuggee_crash")), QUrl::fromLocalFile(f.canonicalFilePath())); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == QLatin1String("ts")) { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; m_core->debugController()->variableCollection()->variableWidgetShown(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString(QStringLiteral("t\\\"t")); // the actual content const QString quotedTestString(QStringLiteral(R"("t\\\"t")")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string WAIT_FOR_A_WHILE(session, 3000); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); QEXPECT_FAIL("", "LLDB 4.0 cannot deal with string literal in expression when debugging, causing memory access error", Abort); COMPARE_DATA(variableCollection()->index(0, 1, i), quotedTestString); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); WAIT_FOR_A_WHILE(session, 100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QStringLiteral("[%0]").arg(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QStringLiteral("[%0]").arg(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\0'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); auto v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); COMPARE_DATA(variableCollection()->indexForItem(v, 1), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(watchVariableAt(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void LldbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 300); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand(QStringLiteral("print i")); WAIT_FOR_A_WHILE(session, 500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } void LldbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_crash"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void LldbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeeqt"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRunLldbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write(QStringLiteral("break set --file %1 --line 35\n").arg(findSourceFile("debugee.cpp")).toUtf8()); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("argc")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } TestDebugSession* session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } void LldbTest::testSpecialPath() { QSKIP("Skipping... lldb-mi itself can't handle path with space in application dir"); TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable(QStringLiteral("path with space/debuggee_spacedebugee")); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("spacedebugee.cpp:30")); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void KDevMI::LLDB::LldbTest::testEnvironmentCd() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); auto path = KIO::upUrl(findExecutable(QStringLiteral("path with space/debuggee_spacedebugee"))); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debuggee_debugeepath")), path); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << path.toLocalFile()); } -QTEST_MAIN(KDevMI::LLDB::LldbTest); +QTEST_MAIN(KDevMI::LLDB::LldbTest) #include "test_lldb.moc" diff --git a/plugins/projectfilter/tests/test_projectfilter.cpp b/plugins/projectfilter/tests/test_projectfilter.cpp index a94463b1c9..8af4241c20 100644 --- a/plugins/projectfilter/tests/test_projectfilter.cpp +++ b/plugins/projectfilter/tests/test_projectfilter.cpp @@ -1,398 +1,398 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_projectfilter.h" #include #include #include #include #include #include "../projectfilter.h" -QTEST_GUILESS_MAIN(TestProjectFilter); +QTEST_GUILESS_MAIN(TestProjectFilter) using namespace KDevelop; typedef QSharedPointer TestFilter; Q_DECLARE_METATYPE(TestFilter) namespace { const bool Invalid = false; const bool Valid = true; const bool Folder = true; const bool File = false; struct MatchTest { QString path; bool isFolder; bool shouldMatch; }; void addTests(const QString& tag, const TestProject& project, const TestFilter& filter, MatchTest* tests, uint numTests) { for (uint i = 0; i < numTests; ++i) { const MatchTest& test = tests[i]; QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; if (test.isFolder) { // also test folder with trailing slash - should not make a difference QTest::newRow(qstrdup(qPrintable(tag + ':' + test.path + '/'))) << filter << Path(project.path(), test.path) << test.isFolder << test.shouldMatch; } } } ///FIXME: remove once we can use c++11 #define ADD_TESTS(tag, project, filter, tests) addTests(QStringLiteral(tag), project, filter, tests, sizeof(tests) / sizeof(tests[0])) struct BenchData { BenchData(const Path &path = Path(), bool isFolder = false) : path(path) , isFolder(isFolder) {} Path path; bool isFolder; }; } Q_DECLARE_METATYPE(QVector) void TestProjectFilter::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType >(); } void TestProjectFilter::cleanupTestCase() { TestCore::shutdown(); } void TestProjectFilter::match() { QFETCH(TestFilter, filter); QFETCH(KDevelop::Path, path); QFETCH(bool, isFolder); QFETCH(bool, expectedIsValid); QCOMPARE(filter->isValid(path, isFolder), expectedIsValid); } void TestProjectFilter::match_data() { QTest::addColumn("filter"); QTest::addColumn("path"); QTest::addColumn("isFolder"); QTest::addColumn("expectedIsValid"); { // test default filters const TestProject project; TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("folder/folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("folder/file"), File, Valid}, {QStringLiteral(".file"), File, Invalid}, {QStringLiteral(".folder"), Folder, Invalid}, {QStringLiteral("folder/.folder"), Folder, Invalid}, {QStringLiteral("folder/.file"), File, Invalid}, {QStringLiteral(".git"), Folder, Invalid}, {QStringLiteral(".gitignore"), File, Valid}, {QStringLiteral(".gitmodules"), File, Valid}, {QStringLiteral("_darcs"), Folder, Invalid}, {QStringLiteral("_svn"), Folder, Invalid}, {QStringLiteral(".svn"), Folder, Invalid}, {QStringLiteral("CVS"), Folder, Invalid}, {QStringLiteral("SCCS"), Folder, Invalid}, {QStringLiteral(".hg"), Folder, Invalid}, {QStringLiteral(".bzr"), Folder, Invalid}, {QStringLiteral("foo.o"), File, Invalid}, {QStringLiteral("foo.so"), File, Invalid}, {QStringLiteral("foo.so.1"), File, Invalid}, {QStringLiteral("foo.a"), File, Invalid}, {QStringLiteral("moc_foo.cpp"), File, Invalid}, {QStringLiteral("ui_foo.h"), File, Invalid}, {QStringLiteral("qrc_foo.cpp"), File, Invalid}, {QStringLiteral("foo.cpp~"), File, Invalid}, {QStringLiteral(".foo.cpp.kate-swp"), File, Invalid}, {QStringLiteral(".foo.cpp.swp"), File, Invalid} }; ADD_TESTS("default", project, filter, tests); } { // test exclude files, basename const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("file.cpp"), File, Invalid}, {QStringLiteral("folder.cpp"), Folder, Valid}, {QStringLiteral("folder/file.cpp"), File, Invalid}, {QStringLiteral("folder/folder.cpp"), Folder, Valid} }; ADD_TESTS("exclude:*.cpp", project, filter, tests); } { // test excludes on folders const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("foo"), Filter::Folders)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Valid}, {QStringLiteral("foo"), Folder, Invalid}, {QStringLiteral("folder/file"), File, Valid}, {QStringLiteral("folder/foo"), Folder, Invalid}, {QStringLiteral("folder/foo"), File, Valid} }; ADD_TESTS("exclude:foo", project, filter, tests); } { // test includes const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files)) << Filter(SerializedFilter(QStringLiteral("*.cpp"), Filter::Files, Filter::Inclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Invalid}, {QStringLiteral("file.cpp"), File, Valid}, {QStringLiteral(".file.cpp"), File, Valid}, {QStringLiteral("folder/file.cpp"), File, Valid}, {QStringLiteral("folder/.file.cpp"), File, Valid} }; ADD_TESTS("include:*.cpp", project, filter, tests); project.projectConfiguration(); } { // test mixed stuff const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("*"), Filter::Files, Filter::Exclusive)) << Filter(SerializedFilter(QStringLiteral("*.inc"), Filter::Files, Filter::Inclusive)) << Filter(SerializedFilter(QStringLiteral("*ex.inc"), Filter::Files, Filter::Exclusive)) << Filter(SerializedFilter(QStringLiteral("bar"), Filter::Folders, Filter::Exclusive)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("folder"), Folder, Valid}, {QStringLiteral("file"), File, Invalid}, {QStringLiteral("file.inc"), File, Valid}, {QStringLiteral("file.ex.inc"), File, Invalid}, {QStringLiteral("folder/file"), File, Invalid}, {QStringLiteral("folder/file.inc"), File, Valid}, {QStringLiteral("folder/file.ex.inc"), File, Invalid}, {QStringLiteral("bar"), Folder, Invalid}, }; ADD_TESTS("mixed", project, filter, tests); } { // relative path const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("/foo/*bar"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foo"), Folder, Valid}, {QStringLiteral("bar"), File, Valid}, {QStringLiteral("foo/bar"), Folder, Invalid}, {QStringLiteral("foo/bar"), File, Invalid}, {QStringLiteral("foo/asdf/bar"), Folder, Invalid}, {QStringLiteral("foo/asdf/bar"), File, Invalid}, {QStringLiteral("foo/asdf_bar"), Folder, Invalid}, {QStringLiteral("foo/asdf_bar"), File, Invalid}, {QStringLiteral("asdf/bar"), File, Valid}, {QStringLiteral("asdf/foo/bar"), File, Valid}, }; ADD_TESTS("relative", project, filter, tests); } { // trailing slash const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("bar/"), Filter::Targets(Filter::Files | Filter::Folders))); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foo"), Folder, Valid}, {QStringLiteral("bar"), File, Valid}, {QStringLiteral("bar"), Folder, Invalid}, {QStringLiteral("foo/bar"), File, Valid}, {QStringLiteral("foo/bar"), Folder, Invalid} }; ADD_TESTS("trailingslash", project, filter, tests); } { // escaping const TestProject project; const Filters filters = Filters() << Filter(SerializedFilter(QStringLiteral("foo\\*bar"), Filter::Files)); TestFilter filter(new ProjectFilter(&project, filters)); QTest::newRow("projectRoot") << filter << project.path() << Folder << Valid; QTest::newRow("project.kdev4") << filter << project.projectFile() << File << Invalid; MatchTest tests[] = { //{path, isFolder, isValid} {QStringLiteral(".kdev4"), Folder, Invalid}, {QStringLiteral("foobar"), Folder, Valid}, {QStringLiteral("fooasdfbar"), File, Valid}, {QStringLiteral("foo*bar"), File, Invalid}, {QStringLiteral("foo/bar"), Folder, Valid} }; ADD_TESTS("escaping", project, filter, tests); } } static QVector createBenchData(const Path& base, int folderDepth, int foldersPerFolder, int filesPerFolder) { QVector data; data << BenchData(base, true); for(int i = 0; i < filesPerFolder; ++i) { if (i % 2) { data << BenchData(Path(base, QStringLiteral("file%1.cpp").arg(i)), false); } else { data << BenchData(Path(base, QStringLiteral("file%1.h").arg(i)), true); } } for(int i = 0; i < foldersPerFolder && folderDepth > 0; ++i) { data += createBenchData(Path(base, QStringLiteral("folder%1").arg(i)), folderDepth - 1, foldersPerFolder, filesPerFolder); } return data; } void TestProjectFilter::bench() { QFETCH(TestFilter, filter); QFETCH(QVector, data); QBENCHMARK { foreach(const BenchData& bench, data) { filter->isValid(bench.path, bench.isFolder); } } } void TestProjectFilter::bench_data() { QTest::addColumn("filter"); QTest::addColumn >("data"); const TestProject project; QVector > dataSets = QVector >() << createBenchData(project.path(), 3, 5, 10) << createBenchData(project.path(), 3, 5, 20) << createBenchData(project.path(), 4, 5, 10) << createBenchData(project.path(), 3, 10, 10); { TestFilter filter(new ProjectFilter(&project, Filters())); foreach(const QVector& data, dataSets) { QTest::newRow(QByteArray("baseline-" + QByteArray::number(data.size()))) << filter << data; } } { TestFilter filter(new ProjectFilter(&project, deserialize(defaultFilters()))); foreach(const QVector& data, dataSets) { QTest::newRow(QByteArray("defaults-" + QByteArray::number(data.size()))) << filter << data; } } } diff --git a/plugins/qmakemanager/tests/test_qmakefile.cpp b/plugins/qmakemanager/tests/test_qmakefile.cpp index 4bdf20d02b..c5feb313c6 100644 --- a/plugins/qmakemanager/tests/test_qmakefile.cpp +++ b/plugins/qmakemanager/tests/test_qmakefile.cpp @@ -1,678 +1,678 @@ /* KDevelop QMake Support * * Copyright 2010 Milian Wolff * * 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 "test_qmakefile.h" #include "qmakefile.h" #include "variablereferenceparser.h" #include "qmakeprojectfile.h" #include "qmakemkspecs.h" #include #include #include #include #include #include #include #include #include #include -QTEST_MAIN(TestQMakeFile); +QTEST_MAIN(TestQMakeFile) typedef QHash DefineHash; Q_DECLARE_METATYPE(QMakeFile::VariableMap) Q_DECLARE_METATYPE(DefineHash) namespace QTest { template <> char* toString(const QStringList& list) { QByteArray ba; if (list.isEmpty()) { ba = "()"; } else { ba = "(\"" + list.join(QStringLiteral("\", \"")).toLocal8Bit() + "\")"; } return qstrdup(ba.data()); } template <> char* toString(const QMakeFile::VariableMap& variables) { QByteArray ba = "VariableMap("; QMakeFile::VariableMap::const_iterator it = variables.constBegin(); while (it != variables.constEnd()) { ba += "["; ba += it.key().toLocal8Bit(); ba += "] = "; ba += toString(it.value()); ++it; if (it != variables.constEnd()) { ba += ", "; } } ba += ")"; return qstrdup(ba.data()); } } QHash setDefaultMKSpec(QMakeProjectFile& file) { static const QHash qmvars = QMakeConfig::queryQMake(QMakeConfig::qmakeExecutable(nullptr)); static const QString specFile = QMakeConfig::findBasicMkSpec(qmvars); if (!QFile::exists(specFile)) { qDebug() << "mkspec file does not exist:" << specFile; return {}; } QMakeMkSpecs* mkspecs = new QMakeMkSpecs(specFile, qmvars); mkspecs->read(); file.setMkSpecs(mkspecs); return qmvars; } void TestQMakeFile::varResolution() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, variables); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeFile file(tmpfile.fileName()); QVERIFY(file.read()); QCOMPARE(file.variableMap(), variables); } void TestQMakeFile::varResolution_data() { QTest::addColumn("fileContents"); QTest::addColumn("variables"); { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); QTest::newRow("simple") << "VAR1 = 1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("1"); QTest::newRow("var-in-var") << "VAR1 = 1\nVAR2 = $$VAR1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo"); QTest::newRow("curlyvar") << "VAR1 = foo\nVAR2 = $${VAR1}\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QProcessEnvironment::systemEnvironment().value(QStringLiteral("USER")); QTest::newRow("qmakeshell") << "VAR1 = $$(USER)\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("path") << "VAR1 = foo\nVAR2 = $$VAR1/bar\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR_1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR_2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("var-underscore") << "VAR_1 = foo\nVAR_2 = $$VAR_1/bar" << variables; } } void TestQMakeFile::referenceParser() { QFETCH(QString, var); VariableReferenceParser parser; parser.setContent(var); QVERIFY(parser.parse()); } void TestQMakeFile::referenceParser_data() { QTest::addColumn("var"); QTest::newRow("dot") << "."; QTest::newRow("dotdot") << ".."; } void TestQMakeFile::libTarget() { QFETCH(QString, target); QFETCH(QString, resolved); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << "TARGET = " << target << "\nTEMPLATE = lib\n"; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); QCOMPARE(file.targets(), QStringList() << resolved); } void TestQMakeFile::libTarget_data() { QTest::addColumn("target"); QTest::addColumn("resolved"); QTest::newRow("simple") << "MyLib" << "MyLib"; QTest::newRow("qtLibraryTarget") << "$$qtLibraryTarget(MyLib)" << "MyLib"; QTest::newRow("qtLibraryTarget-Var") << "MyLib\nTARGET = $$qtLibraryTarget($$TARGET)" << "MyLib"; } void TestQMakeFile::defines() { QFETCH(QString, fileContents); QFETCH(DefineHash, expectedDefines); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); QList list = file.defines(); QCOMPARE(list.size(), expectedDefines.size()); foreach (QMakeProjectFile::DefinePair define, list) { QVERIFY(expectedDefines.find(define.first) != expectedDefines.end()); QCOMPARE(define.second, expectedDefines[define.first]); } } void TestQMakeFile::defines_data() { QTest::addColumn("fileContents"); QTest::addColumn("expectedDefines"); { DefineHash list; list.insert(QStringLiteral("VAR1"), QLatin1String("")); QTest::newRow("Simple define") << "DEFINES += VAR1" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); QTest::newRow("Define with value") << "DEFINES += ANSWER=42" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); list.insert(QStringLiteral("ANOTHER_DEFINE"), QLatin1String("")); QTest::newRow("Multiple defines") << "DEFINES += ANSWER=42 ANOTHER_DEFINE" << list; } } void TestQMakeFile::replaceFunctions_data() { QTest::addColumn("fileContents"); QTest::addColumn("definedVariables"); QTest::addColumn("undefinedVariables"); { QString contents = "defineReplace(test) {\n" " FOO = $$1\n" " return($$FOO)\n" "}\n" "BAR = $$test(asdf)\n"; QMakeFile::VariableMap vars; vars[QStringLiteral("BAR")] = QStringList() << QStringLiteral("asdf"); QStringList undefined; undefined << QStringLiteral("FOO") << QStringLiteral("1"); QTest::newRow("defineReplace-1") << contents << vars << undefined; } } void TestQMakeFile::replaceFunctions() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, definedVariables); QFETCH(QStringList, undefinedVariables); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QMakeFile::VariableMap::const_iterator it = definedVariables.constBegin(); while (it != definedVariables.constEnd()) { QCOMPARE(file.variableValues(it.key()), it.value()); ++it; } foreach (const QString& var, undefinedVariables) { QVERIFY(!file.containsVariable(var)); } } void TestQMakeFile::qtIncludeDirs_data() { QTest::addColumn("fileContents"); QTest::addColumn("modules"); QTest::addColumn("missingModules"); { QStringList list; list << QStringLiteral("core") << QStringLiteral("gui"); QTest::newRow("defaults") << "" << list; } { QStringList list; list << QStringLiteral("core"); QTest::newRow("minimal") << "QT -= gui" << list; } { QStringList modules; modules << QStringLiteral("core") << QStringLiteral("gui") << QStringLiteral("network") << QStringLiteral("opengl") << QStringLiteral("phonon") << QStringLiteral("script") << QStringLiteral("scripttools") << QStringLiteral("sql") << QStringLiteral("svg") << QStringLiteral("webkit") << QStringLiteral("xml") << QStringLiteral("xmlpatterns") << QStringLiteral("qt3support") << QStringLiteral("designer") << QStringLiteral("uitools") << QStringLiteral("help") << QStringLiteral("assistant") << QStringLiteral("qtestlib") << QStringLiteral("testlib") << QStringLiteral("qaxcontainer") << QStringLiteral("qaxserver") << QStringLiteral("dbus") << QStringLiteral("declarative"); foreach (const QString& module, modules) { QStringList expected; expected << module; if (module != QLatin1String("core")) { expected << QStringLiteral("core"); } QTest::newRow(qPrintable(module)) << QStringLiteral("QT = %1").arg(module) << expected; } } } void TestQMakeFile::qtIncludeDirs() { QFETCH(QString, fileContents); QFETCH(QStringList, modules); QMap moduleMap; moduleMap[QStringLiteral("core")] = QStringLiteral("QtCore"); moduleMap[QStringLiteral("gui")] = QStringLiteral("QtGui"); moduleMap[QStringLiteral("network")] = QStringLiteral("QtNetwork"); moduleMap[QStringLiteral("opengl")] = QStringLiteral("QtOpenGL"); moduleMap[QStringLiteral("phonon")] = QStringLiteral("Phonon"); moduleMap[QStringLiteral("script")] = QStringLiteral("QtScript"); moduleMap[QStringLiteral("scripttools")] = QStringLiteral("QtScriptTools"); moduleMap[QStringLiteral("sql")] = QStringLiteral("QtSql"); moduleMap[QStringLiteral("svg")] = QStringLiteral("QtSvg"); moduleMap[QStringLiteral("webkit")] = QStringLiteral("QtWebKit"); moduleMap[QStringLiteral("xml")] = QStringLiteral("QtXml"); moduleMap[QStringLiteral("xmlpatterns")] = QStringLiteral("QtXmlPatterns"); moduleMap[QStringLiteral("qt3support")] = QStringLiteral("Qt3Support"); moduleMap[QStringLiteral("designer")] = QStringLiteral("QtDesigner"); moduleMap[QStringLiteral("uitools")] = QStringLiteral("QtUiTools"); moduleMap[QStringLiteral("help")] = QStringLiteral("QtHelp"); moduleMap[QStringLiteral("assistant")] = QStringLiteral("QtAssistant"); moduleMap[QStringLiteral("qtestlib")] = QStringLiteral("QtTest"); moduleMap[QStringLiteral("testlib")] = QStringLiteral("QtTest"); moduleMap[QStringLiteral("qaxcontainer")] = QStringLiteral("ActiveQt"); moduleMap[QStringLiteral("qaxserver")] = QStringLiteral("ActiveQt"); moduleMap[QStringLiteral("dbus")] = QStringLiteral("QtDBus"); moduleMap[QStringLiteral("declarative")] = QStringLiteral("QtDeclarative"); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); const QStringList includes = file.includeDirectories(); // should always be there QVERIFY(includes.contains(qmvars["QT_INSTALL_HEADERS"])); for (QMap::const_iterator it = moduleMap.constBegin(); it != moduleMap.constEnd(); ++it) { QFileInfo include(qmvars[QStringLiteral("QT_INSTALL_HEADERS")] + "/" + it.value()); bool shouldBeIncluded = include.exists(); if (shouldBeIncluded) { shouldBeIncluded = modules.contains(it.key()); if (!shouldBeIncluded) { foreach (const QString& module, modules) { if (module != it.key() && moduleMap.value(module) == it.value()) { shouldBeIncluded = true; break; } } } } QCOMPARE((bool)includes.contains(include.filePath()), shouldBeIncluded); } } void TestQMakeFile::testInclude() { QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QTemporaryFile includeFile(tempDir.path() + "/qmake-include"); QVERIFY(includeFile.open()); includeFile.write("DEFINES += SOME_INCLUDE_DEF\n" "SOURCES += includedFile.cpp\n" "INCLUDEPATH += $$PWD\n" "QT += webkit\n"); includeFile.close(); QTemporaryFile baseFile; baseFile.open(); baseFile.write("TEMPLATE = app\n" "TARGET = includeTest\n" "QT += network\n" "DEFINES += SOME_DEF\n" "SOURCES += file.cpp\n" /* "CONFIG += console" "# Comment to enable Debug Messages" "DEFINES += QT_NO_DEBUG_OUTPUT" "DESTDIR = ../bin" "RESOURCES = phantomjs.qrc" "HEADERS += csconverter.h \\" " phantom.h \\" " webpage.h \\" " consts.h \\" " utils.h \\" " networkaccessmanager.h \\" " cookiejar.h \\" " filesystem.h \\" " terminal.h \\" " encoding.h \\" " config.h \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.h \\" " webserver.h" "SOURCES += phantom.cpp \\" " webpage.cpp \\" " main.cpp \\" " csconverter.cpp \\" " utils.cpp \\" " networkaccessmanager.cpp \\" " cookiejar.cpp \\" " filesystem.cpp \\" " terminal.cpp \\" " encoding.cpp \\" " config.cpp \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.c \\" " webserver.cpp" "" "OTHER_FILES += usage.txt \\" " bootstrap.js \\" " configurator.js \\" " modules/fs.js \\" " modules/webpage.js \\" " modules/webserver.js" "" */ "include(" + includeFile.fileName().toLocal8Bit() + ")\n"); baseFile.close(); QMakeProjectFile file(baseFile.fileName()); const auto qmvars = setDefaultMKSpec(file); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(file.read()); QCOMPARE(file.variableValues("DEFINES"), QStringList() << "SOME_DEF" << "SOME_INCLUDE_DEF"); QCOMPARE(file.variableValues("SOURCES"), QStringList() << "file.cpp" << "includedFile.cpp"); QCOMPARE(file.variableValues("QT"), QStringList() << "core" << "gui" << "network" << "webkit"); // verify that include path was properly propagated QVERIFY(file.includeDirectories().contains(tempDir.path())); } void TestQMakeFile::globbing_data() { QTest::addColumn("files"); QTest::addColumn("pattern"); QTest::addColumn("matches"); QTest::newRow("wildcard-simple") << (QStringList() << QStringLiteral("foo.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp")); QTest::newRow("wildcard-extended") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-multiple") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp *.h" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-subdir") << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp") << QStringLiteral("asdf/asdf.cpp")) << "foo*/*.cpp" << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp")); QTest::newRow("bracket") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp") << QStringLiteral("fooX.cpp")) << "foo[0-9].cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp")); QTest::newRow("questionmark") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp") << QStringLiteral("foo.cpp") << QStringLiteral("fooXY.cpp")) << "foo?.cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp")); QTest::newRow("mixed") << (QStringList() << QStringLiteral("foo/asdf/test.cpp") << QStringLiteral("fooX/asdf1/test.cpp")) << "foo?/asdf[0-9]/*.cpp" << (QStringList() << QStringLiteral("fooX/asdf1/test.cpp")); } void TestQMakeFile::globbing() { QFETCH(QStringList, files); QFETCH(QString, pattern); QFETCH(QStringList, matches); QTemporaryDir tempDir; QDir tempDirDir(tempDir.path()); QVERIFY(tempDir.isValid()); foreach (const QString& file, files) { QVERIFY(tempDirDir.mkpath(QFileInfo(file).path())); QFile f(tempDir.path() + '/' + file); QVERIFY(f.open(QIODevice::WriteOnly)); } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write(("SOURCES = " + pattern + "\n").toUtf8()); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); QStringList actual; foreach (QString path, pro.files()) { actual << path.remove(tempDir.path() + '/'); } std::sort(actual.begin(), actual.end()); std::sort(matches.begin(), matches.end()); QCOMPARE(actual, matches); } void TestQMakeFile::benchGlobbing() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = fo?der[0-9]/*.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, files * folders); } void TestQMakeFile::benchGlobbingNoPattern() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = folder0/file1.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); const auto qmvars = setDefaultMKSpec(pro); if (qmvars.isEmpty()) { QSKIP("Problem querying QMake, skipping test function"); } QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, 1); } diff --git a/plugins/qmakemanager/tests/test_qmakeproject.cpp b/plugins/qmakemanager/tests/test_qmakeproject.cpp index e4306bb322..bb70ee0170 100644 --- a/plugins/qmakemanager/tests/test_qmakeproject.cpp +++ b/plugins/qmakemanager/tests/test_qmakeproject.cpp @@ -1,145 +1,145 @@ /* KDevelop QMake Support * * Copyright 2011 Julien Desgats * * 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 "test_qmakeproject.h" #include "../qmakeconfig.h" #include "qmaketestconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -QTEST_MAIN(TestQMakeProject); +QTEST_MAIN(TestQMakeProject) using namespace KDevelop; TestQMakeProject::TestQMakeProject(QObject* parent) : QObject(parent) { qRegisterMetaType(); } TestQMakeProject::~TestQMakeProject() { } void TestQMakeProject::initTestCase() { AutoTestShell::init({ "KDevQMakeManager", "KDevQMakeBuilder", "KDevMakeBuilder", "KDevStandardOutputView" }); TestCore::initialize(); } void TestQMakeProject::cleanupTestCase() { Core::self()->cleanup(); } void TestQMakeProject::testBuildDirectory_data() { QTest::addColumn("projectName"); // name of the project (both directory and .kde4 file) QTest::addColumn("target"); // directory to compile from project root QTest::addColumn("expected"); // expected build directory from build dir QTest::newRow("Basic Project") << "basic_project" << "" << ""; QTest::newRow("Subdirs Project (root)") << "subdirs_project" << "" << ""; QTest::newRow("Subdirs Project (dir_a)") << "subdirs_project" << "dir_a" << "dir_a"; } void TestQMakeProject::testBuildDirectory() { QFETCH(QString, projectName); QFETCH(QString, target); QFETCH(QString, expected); const QString buildDir = QStringLiteral("/tmp/some/path"); // some dummy directory to build (nothing will be built anyway) foreach (IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); } // setup project config, to avoid build dir chooser dialog popping up { // note: all checks from QMakeProjectManager::projectNeedsConfiguration must be satisfied const QString fileName = QStringLiteral("%1/%2/.kdev4/%3.kdev4").arg(QMAKE_TESTS_PROJECTS_DIR).arg(projectName).arg(projectName); KConfig cfg(fileName); KConfigGroup group(&cfg, QMakeConfig::CONFIG_GROUP); group.writeEntry(QMakeConfig::BUILD_FOLDER, buildDir); group.writeEntry(QMakeConfig::QMAKE_EXECUTABLE, QMAKE_TESTS_QMAKE_EXECUTABLE); group.sync(); /// create subgroup for one build dir KConfigGroup buildDirGroup = KConfigGroup(&cfg, QMakeConfig::CONFIG_GROUP).group(buildDir); buildDirGroup.writeEntry(QMakeConfig::QMAKE_EXECUTABLE, QMAKE_TESTS_QMAKE_EXECUTABLE); buildDirGroup.sync(); QVERIFY(QFileInfo::exists(fileName)); } // opens project with kdevelop const QUrl projectUrl = QUrl::fromLocalFile( QStringLiteral("%1/%2/%3.kdev4").arg(QMAKE_TESTS_PROJECTS_DIR).arg(projectName).arg(projectName)); ICore::self()->projectController()->openProject(projectUrl); // wait for loading finished QSignalSpy spy(ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); bool gotSignal = spy.wait(30000); QVERIFY2(gotSignal, "Timeout while waiting for opened signal"); IProject* project = ICore::self()->projectController()->findProjectByName(projectName); // adds expected directory to our base path Path expectedPath(Path(buildDir), expected); // path for files to build Path buildUrl(QStringLiteral("%1/%2/%3").arg(QMAKE_TESTS_PROJECTS_DIR).arg(projectName).arg(target)); QList buildItems = project->foldersForPath(IndexedString(buildUrl.pathOrUrl())); QCOMPARE(buildItems.size(), 1); IBuildSystemManager* buildManager = project->buildSystemManager(); const auto buildFolder = buildItems.first(); const Path actual = buildManager->buildDirectory(buildFolder); QCOMPARE(actual, expectedPath); auto buildJob = buildManager->builder()->configure(project); QVERIFY(buildJob->exec()); } diff --git a/plugins/qmljs/duchain/tests/test_qmljscontexts.cpp b/plugins/qmljs/duchain/tests/test_qmljscontexts.cpp index e1dfd35501..3eb2cad1df 100644 --- a/plugins/qmljs/duchain/tests/test_qmljscontexts.cpp +++ b/plugins/qmljs/duchain/tests/test_qmljscontexts.cpp @@ -1,146 +1,146 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_qmljscontexts.h" #include "../helper.h" #include "../parsesession.h" #include "../declarationbuilder.h" #include #include #include #include -QTEST_GUILESS_MAIN(TestContexts); +QTEST_GUILESS_MAIN(TestContexts) using namespace KDevelop; void TestContexts::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); QmlJS::registerDUChainItems(); } void TestContexts::cleanupTestCase() { TestCore::shutdown(); } void TestContexts::testFunctionContext() { QFETCH(QString, code); QFETCH(RangeInRevision, argCtxRange); QFETCH(RangeInRevision, bodyCtxRange); const IndexedString file(QUrl(QStringLiteral("file:///internal/%1-functionContext.js").arg(qrand()))); ParseSession session(file, code, 0); QVERIFY(session.ast()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::JavaScript); DeclarationBuilder builder(&session); ReferencedTopDUContext top = builder.build(file, session.ast()); QVERIFY(top); DUChainReadLocker lock; QCOMPARE(top->type(), DUContext::Global); // the function arguments (containing the prototype context and the function body) QCOMPARE(top->childContexts().count(), 3); // module, exports, the function DUContext* argCtx = top->childContexts().at(2); QCOMPARE(argCtx->type(), DUContext::Function); QCOMPARE(argCtx->range(), argCtxRange); QCOMPARE(argCtx->childContexts().size(), 2); // The prototype context then the body context DUContext* bodyCtx = argCtx->childContexts().at(1); QVERIFY(bodyCtx); QCOMPARE(bodyCtx->type(), DUContext::Other); QCOMPARE(bodyCtx->range(), bodyCtxRange); } void TestContexts::testFunctionContext_data() { QTest::addColumn("code"); QTest::addColumn("argCtxRange"); QTest::addColumn("bodyCtxRange"); // 0 1 // 012345678901234567890 QTest::newRow("empty") << "function foo() {;}" << RangeInRevision(0, 12, 0, 18) << RangeInRevision(0, 15, 0, 18); // 0 1 2 3 // 01234567890123456789012345678901234567890 QTest::newRow("args") << "function foo(arg1, arg2, arg3) {;}" << RangeInRevision(0, 12, 0, 34) << RangeInRevision(0, 31, 0, 34); // 0 1 2 // 0123456789012345678901234567890 QTest::newRow("newline") << "function foo() {;\n}" << RangeInRevision(0, 12, 1, 1) << RangeInRevision(0, 15, 1, 1); } void TestContexts::testQMLContext() { const IndexedString file(QUrl(QStringLiteral("file:///internal/testQMLContext.qml"))); ParseSession session(file, "Text {\n" " id: main\n" " Text {\n" " id: child1\n" " }\n" " Text {\n" " id: child2\n" " }\n" "}\n", 0); QVERIFY(session.ast()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::Qml); DeclarationBuilder builder(&session); ReferencedTopDUContext top = builder.build(file, session.ast()); QVERIFY(top); DUChainReadLocker lock; QCOMPARE(top->type(), DUContext::Global); QCOMPARE(top->childContexts().count(), 3); // module, exports, Text DUContext* mainCtx = top->childContexts().at(2); QCOMPARE(mainCtx->type(), DUContext::Class); QCOMPARE(mainCtx->range(), RangeInRevision(0, 6, 8, 0)); QCOMPARE(mainCtx->childContexts().size(), 2); DUContext* child1Ctx = mainCtx->childContexts().first(); QCOMPARE(child1Ctx->type(), DUContext::Class); QCOMPARE(child1Ctx->range(), RangeInRevision(2, 8, 4, 2)); QCOMPARE(child1Ctx->childContexts().size(), 0); DUContext* child2Ctx = mainCtx->childContexts().last(); QCOMPARE(child2Ctx->type(), DUContext::Class); QCOMPARE(child2Ctx->range(), RangeInRevision(5, 8, 7, 2)); QCOMPARE(child2Ctx->childContexts().size(), 0); } diff --git a/plugins/qmljs/duchain/tests/test_qmljsdeclarations.cpp b/plugins/qmljs/duchain/tests/test_qmljsdeclarations.cpp index 36b9e19f8f..fd48ac4aa4 100644 --- a/plugins/qmljs/duchain/tests/test_qmljsdeclarations.cpp +++ b/plugins/qmljs/duchain/tests/test_qmljsdeclarations.cpp @@ -1,289 +1,289 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_qmljsdeclarations.h" #include "../helper.h" #include "../parsesession.h" #include "../declarationbuilder.h" #include "../cache.h" #include #include #include #include #include #include #include #include #include -QTEST_GUILESS_MAIN(TestDeclarations); +QTEST_GUILESS_MAIN(TestDeclarations) using namespace KDevelop; void TestDeclarations::initTestCase() { AutoTestShell::init({"kdevqmljs"}); TestCore::initialize(Core::NoUi); QmlJS::registerDUChainItems(); } void TestDeclarations::cleanupTestCase() { TestCore::shutdown(); } void TestDeclarations::testJSProblems() { const IndexedString file(QUrl(QStringLiteral("file:///internal/jsproblems.js"))); ParseSession session(file, "function f(a) {}\n" "f(2);\n" "f(true);\n", 0); QVERIFY(session.ast()); DeclarationBuilder builder(&session); builder.build(file, session.ast()); auto problems = session.problems(); QCOMPARE(problems.count(), 1); QCOMPARE((KTextEditor::Range)problems.at(0)->finalLocation(), KTextEditor::Range(2, 2, 2, 6)); } void TestDeclarations::testFunction() { const IndexedString file(QUrl(QStringLiteral("file:///internal/functionArgs.js"))); // 0 1 2 3 // 01234567890123456789012345678901234567890 ParseSession session(file, "/**\n * some comment\n */\n" "function foo(arg1, arg2, arg3) { var i = 0; }", 0); QVERIFY(session.ast()); QVERIFY(session.problems().isEmpty()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::JavaScript); DeclarationBuilder builder(&session); ReferencedTopDUContext top = builder.build(file, session.ast()); QVERIFY(top); DUChainReadLocker lock; QCOMPARE(top->localDeclarations().size(), 3); // module, exports and foo FunctionDeclaration* fooDec = dynamic_cast(top->localDeclarations().at(2)); QVERIFY(fooDec); QCOMPARE(fooDec->range(), RangeInRevision(3, 9, 3, 12)); QCOMPARE(QString::fromUtf8(fooDec->comment()), QString("some comment")); QVERIFY(fooDec->internalContext()); DUContext* argCtx = fooDec->internalContext(); QCOMPARE(argCtx->localDeclarations().size(), 3); Declaration* arg1 = argCtx->localDeclarations().at(0); QCOMPARE(arg1->identifier().toString(), QString("arg1")); QCOMPARE(arg1->range(), RangeInRevision(3, 13, 3, 17)); Declaration* arg2 = argCtx->localDeclarations().at(1); QCOMPARE(arg2->identifier().toString(), QString("arg2")); QCOMPARE(arg2->range(), RangeInRevision(3, 19, 3, 23)); Declaration* arg3 = argCtx->localDeclarations().at(2); QCOMPARE(arg3->identifier().toString(), QString("arg3")); QCOMPARE(arg3->range(), RangeInRevision(3, 25, 3, 29)); FunctionType::Ptr funType = fooDec->type(); QVERIFY(funType); QVERIFY(funType->returnType().cast()); QCOMPARE(funType->returnType().cast()->dataType(), (uint) IntegralType::TypeVoid); QCOMPARE(argCtx->childContexts().size(), 2); DUContext* bodyCtx = argCtx->childContexts().at(1); QVERIFY(bodyCtx); QVERIFY(bodyCtx->findDeclarations(arg1->identifier()).contains(arg1)); QVERIFY(bodyCtx->findDeclarations(arg2->identifier()).contains(arg2)); QVERIFY(bodyCtx->findDeclarations(arg3->identifier()).contains(arg3)); QCOMPARE(bodyCtx->localDeclarations().count(), 1); } void TestDeclarations::testQMLId() { const IndexedString file(QUrl(QStringLiteral("file:///internal/qmlId.qml"))); ReferencedTopDUContext top; DeclarationPointer oldDec; { // 0 1 2 3 // 01234567890123456789012345678901234567890 ParseSession session(file, "/** file comment **/\n" "import QtQuick 1.0\n" "/**\n * some comment\n */\n" "Text { id: test; Text { id: child; } }", 0); QVERIFY(session.ast()); QVERIFY(session.problems().isEmpty()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::Qml); DeclarationBuilder builder(&session); top = builder.build(file, session.ast()); QVERIFY(top); DUChainReadLocker lock; QCOMPARE(top->localDeclarations().size(), 5); // module, exports, Text, test and child are all in the global scope // First declaration, the anonymous class ClassDeclaration* classDecl = dynamic_cast(top->localDeclarations().at(2)); QVERIFY(classDecl); QCOMPARE(classDecl->abstractType()->toString(), QString("qmlId")); QCOMPARE(classDecl->range(), RangeInRevision(5, 0, 5, 0)); QVERIFY(classDecl->internalContext()); QCOMPARE(classDecl->internalContext()->range(), RangeInRevision(5, 6, 5, 37)); // Second declaration: test Declaration* dec = top->localDeclarations().at(3); QVERIFY(dec); QCOMPARE(dec->identifier().toString(), QString("test")); QCOMPARE(dec->abstractType()->toString(), QString("qmlId")); oldDec = dec; } // test recompile { // 0 1 2 3 // 01234567890123456789012345678901234567890 ParseSession session(file, "/** file comment **/\n" "import QtQuick 1.0\n" "/**\n * some comment\n */\n" "Text { id: test; Text { id: child;}\n" " Text {id: foo;} }", 0); QVERIFY(session.ast()); QVERIFY(session.problems().isEmpty()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::Qml); DeclarationBuilder builder(&session); ReferencedTopDUContext top2 = builder.build(file, session.ast(), top); QVERIFY(top2); QCOMPARE(top2.data(), top.data()); DUChainReadLocker lock; QCOMPARE(top->localDeclarations().size(), 6); // module, exports, Text, test, child and foo // First declaration, the anonymous class ClassDeclaration* classDecl = dynamic_cast(top->localDeclarations().at(2)); QVERIFY(classDecl); QCOMPARE(classDecl->abstractType()->toString(), QString("qmlId")); QCOMPARE(classDecl->range(), RangeInRevision(5, 0, 5, 0)); QVERIFY(classDecl->internalContext()); QCOMPARE(classDecl->internalContext()->range(), RangeInRevision(5, 6, 6, 17)); // Second declaration: test Declaration* dec = top->localDeclarations().at(3); QVERIFY(dec); QCOMPARE(dec->identifier().toString(), QString("test")); QCOMPARE(dec->abstractType()->toString(), QString("qmlId")); } } void TestDeclarations::testProperty() { const IndexedString file(QUrl(QStringLiteral("file:///internal/qmlProperty.qml"))); // 0 1 2 3 // 01234567890123456789012345678901234567890 ParseSession session(file, "Text {\n" " /// some comment\n" " property int foo;\n" "}", 0); QVERIFY(session.ast()); QVERIFY(session.problems().isEmpty()); QCOMPARE(session.language().dialect(), QmlJS::Dialect::Qml); DeclarationBuilder builder(&session); ReferencedTopDUContext top = builder.build(file, session.ast()); QVERIFY(top); DUChainReadLocker lock; QCOMPARE(top->localDeclarations().size(), 3); // module, exports, Text ClassDeclaration* text = dynamic_cast(top->localDeclarations().at(2)); QVERIFY(text); QVERIFY(text->internalContext()); QCOMPARE(text->internalContext()->type(), DUContext::Class); QCOMPARE(text->internalContext()->localDeclarations().size(), 1); ClassMemberDeclaration* foo = dynamic_cast(text->internalContext()->localDeclarations().first()); QVERIFY(foo); QCOMPARE(foo->identifier().toString(), QString("foo")); QVERIFY(foo->abstractType()); QCOMPARE(foo->abstractType()->toString(), QString("int")); QCOMPARE(QString::fromUtf8(foo->comment()), QString("some comment")); } /** * Test that all qmltypes files for built-in QtQuick modules are found on the system. * These files are also available on CI machines, since an installed Qt5 is assumed */ void TestDeclarations::testQMLtypesImportPaths() { KDevelop::IndexedString stubPath; QString path; // QtQuick QML modules path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick"), QStringLiteral("2.0")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtTest"), QStringLiteral("1.1")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.Layouts"), QStringLiteral("1.1")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.Controls"), QStringLiteral("1.1")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.Dialogs"), QStringLiteral("1.1")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.LocalStorage"), QStringLiteral("2.0")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.Particles"), QStringLiteral("2.0")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.Window"), QStringLiteral("2.2")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQuick.XmlListModel"), QStringLiteral("2.0")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); // QtQml QML modules path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtQml.Models"), QStringLiteral("2.3")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); // QtMultimedia QML modules path = QmlJS::Cache::instance().modulePath(stubPath, QStringLiteral("QtMultimedia"), QStringLiteral("5.6")); QVERIFY(QFileInfo::exists(path + "/plugins.qmltypes")); } diff --git a/plugins/qmljs/kdevqmljsplugin.cpp b/plugins/qmljs/kdevqmljsplugin.cpp index 8bd28325af..97d9fadf51 100644 --- a/plugins/qmljs/kdevqmljsplugin.cpp +++ b/plugins/qmljs/kdevqmljsplugin.cpp @@ -1,204 +1,204 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * Copyright (C) 2013 by Sven Brauch * * * * 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 "kdevqmljsplugin.h" #include "qmljsparsejob.h" #include "qmljshighlighting.h" #include "codecompletion/model.h" #include "navigation/propertypreviewwidget.h" #include "duchain/helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevQmlJsSupportFactory, "kdevqmljs.json", registerPlugin(); ) using namespace KDevelop; /// TODO: Extend? See qmljsmodelmanager.h in qt-creator.git class ModelManager: public QmlJS::ModelManagerInterface { Q_OBJECT public: explicit ModelManager(QObject *parent = nullptr); ~ModelManager() override; }; ModelManager::ModelManager(QObject* parent) : QmlJS::ModelManagerInterface(parent) {} ModelManager::~ModelManager() {} KDevQmlJsPlugin::KDevQmlJsPlugin(QObject* parent, const QVariantList& ) : IPlugin(QStringLiteral("kdevqmljssupport"), parent ) , ILanguageSupport() , m_highlighting(new QmlJsHighlighting(this)) , m_refactoring(new BasicRefactoring(this)) , m_modelManager(new ModelManager(this)) { QmlJS::registerDUChainItems(); CodeCompletionModel* codeCompletion = new QmlJS::CodeCompletionModel(this); new KDevelop::CodeCompletion(this, codeCompletion, name()); auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); } KDevQmlJsPlugin::~KDevQmlJsPlugin() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); QmlJS::unregisterDUChainItems(); } ParseJob* KDevQmlJsPlugin::createParseJob(const IndexedString& url) { return new QmlJsParseJob(url, this); } QString KDevQmlJsPlugin::name() const { return QStringLiteral("qml/js"); } ICodeHighlighting* KDevQmlJsPlugin::codeHighlighting() const { return m_highlighting; } BasicRefactoring* KDevQmlJsPlugin::refactoring() const { return m_refactoring; } ContextMenuExtension KDevQmlJsPlugin::contextMenuExtension(Context* context, QWidget* parent) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a QML/JS file, let's add our context menu. m_refactoring->fillContextMenu(cm, context, parent); } return cm; } const QString textFromDoc(const IDocument* doc, const KTextEditor::Range& range) { return doc->textDocument()->line(range.start().line()).mid(range.start().column(), range.end().column()-range.start().column()); -}; +} // Finds how many spaces the given string has at one end. // direction=+1 -> left end of the string, -1 for right end. int spacesAtCorner(const QString& string, int direction = +1) { int spaces = 0; QString::const_iterator it; for ( it = direction == 1 ? string.begin() : string.end()-1 ; it != string.end(); it += direction ) { if ( ! it->isSpace() ) break; spaces += 1; } return spaces; } // Take the given QML line and check if it's a line of the form foo.bar: value. // Return ranges for the key and the value. const QPair parseProperty(const QString& line, const KTextEditor::Cursor& position) { QStringList items = line.split(';'); QString matchingItem; int col_offset = -1; // This is to also support FooAnimation { foo: bar; baz: bang; duration: 200 } // or similar foreach ( const QString& item, items ) { col_offset += item.size() + 1; if ( position.column() < col_offset ) { matchingItem = item; break; } } QStringList split = matchingItem.split(':'); if ( split.size() != 2 ) { // The expression is not of the form foo:bar, thus invalid. return qMakePair(KTextEditor::Range::invalid(), KTextEditor::Range::invalid()); } QString key = split.at(0); QString value = split.at(1); // For animations or similar, remove the trailing '}' if there's no semicolon after the last entry if ( value.trimmed().endsWith('}') ) { col_offset -= value.size() - value.lastIndexOf('}') + 1; value = value.left(value.lastIndexOf('}')-1); } return qMakePair( KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() - key.size() + spacesAtCorner(key, +1) - 1), KTextEditor::Cursor(position.line(), col_offset - value.size() - 1 + spacesAtCorner(key, -1)) ), KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() + spacesAtCorner(value, +1)), KTextEditor::Cursor(position.line(), col_offset + spacesAtCorner(value, -1)) )); -}; +} QWidget* KDevQmlJsPlugin::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if ( doc && doc->textDocument() ) { // Check for a QML property, and construct a property preview widget // if the property key is listed in the supported properties. QPair property = parseProperty(doc->textDocument()->line(position.line()), position); if ( property.first.isValid() && property.second.isValid() ) { Declaration* decl = DUChainUtils::itemUnderCursor(url, property.first.start()).declaration; return PropertyPreviewWidget::constructIfPossible( doc->textDocument(), property.first, property.second, decl, textFromDoc(doc, property.first), textFromDoc(doc, property.second) ); } } // Otherwise, display no special "navigation" widget. return KDevelop::ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); } #include "kdevqmljsplugin.moc" diff --git a/plugins/quickopen/tests/bench_quickopen.cpp b/plugins/quickopen/tests/bench_quickopen.cpp index a39021be6a..e731c70ec4 100644 --- a/plugins/quickopen/tests/bench_quickopen.cpp +++ b/plugins/quickopen/tests/bench_quickopen.cpp @@ -1,161 +1,161 @@ /* * Copyright Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "bench_quickopen.h" #include #include #include #include -QTEST_MAIN(BenchQuickOpen); +QTEST_MAIN(BenchQuickOpen) using namespace KDevelop; BenchQuickOpen::BenchQuickOpen(QObject* parent) : QuickOpenTestBase(Core::NoUi, parent) { } void BenchQuickOpen::getData() { QTest::addColumn("files"); QTest::addColumn("filter"); QTest::newRow("0100-___") << 100 << ""; QTest::newRow("0500-___") << 500 << ""; QTest::newRow("0100-bar") << 100 << "bar"; QTest::newRow("0500-bar") << 500 << "bar"; QTest::newRow("0100-1__") << 100 << "1"; QTest::newRow("0500-1__") << 500 << "1"; QTest::newRow("0100-f/b") << 100 << "f/b"; QTest::newRow("0500-f/b") << 500 << "f/b"; } void BenchQuickOpen::benchProjectFileFilter_addRemoveProject() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; QScopedPointer project(getProjectWithFiles(files)); QBENCHMARK { projectController->addProject(project.data()); projectController->takeProject(project.data()); } } void BenchQuickOpen::benchProjectFileFilter_addRemoveProject_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_reset() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); provider.setFilterText(filter); projectController->addProject(project); QBENCHMARK { provider.reset(); } } void BenchQuickOpen::benchProjectFileFilter_reset_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_setFilter() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QBENCHMARK { provider.setFilterText(filter); provider.setFilterText(QString()); } } void BenchQuickOpen::benchProjectFileFilter_setFilter_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_providerData() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QCOMPARE(provider.itemCount(), uint(files)); provider.setFilterText(filter); QVERIFY(provider.itemCount()); const int itemIdx = provider.itemCount() - 1; QBENCHMARK { QuickOpenDataPointer data = provider.data(itemIdx); data->text(); } } void BenchQuickOpen::benchProjectFileFilter_providerData_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_providerDataIcon() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QCOMPARE(provider.itemCount(), uint(files)); provider.setFilterText(filter); QVERIFY(provider.itemCount()); const int itemIdx = provider.itemCount() - 1; QBENCHMARK { QuickOpenDataPointer data = provider.data(itemIdx); data->icon(); } } void BenchQuickOpen::benchProjectFileFilter_providerDataIcon_data() { getData(); } diff --git a/plugins/quickopen/tests/test_quickopen.cpp b/plugins/quickopen/tests/test_quickopen.cpp index 06390cecbd..492e8d406c 100644 --- a/plugins/quickopen/tests/test_quickopen.cpp +++ b/plugins/quickopen/tests/test_quickopen.cpp @@ -1,393 +1,393 @@ /* * Copyright Milian Wolff * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_quickopen.h" #include #include #include #include -QTEST_MAIN(TestQuickOpen); +QTEST_MAIN(TestQuickOpen) using namespace KDevelop; using ItemList = QVector; using StringList = QVector; TestQuickOpen::TestQuickOpen(QObject* parent) : QuickOpenTestBase(Core::Default, parent) { } void TestQuickOpen::testDuchainFilter() { QFETCH(ItemList, items); QFETCH(QString, filter); QFETCH(ItemList, filtered); auto toStringList = [](const ItemList& items) { QStringList result; for (const DUChainItem& item: items) { result << item.m_text; } return result; }; TestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter); QCOMPARE(toStringList(filterItems.filteredItems()), toStringList(filtered)); } void TestQuickOpen::testDuchainFilter_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); auto i = [](const QString& text) { auto item = DUChainItem(); item.m_text = text; return item; }; auto items = ItemList() << i(QStringLiteral("KTextEditor::Cursor")) << i(QStringLiteral("void KTextEditor::Cursor::explode()")) << i(QStringLiteral("QVector SomeNamespace::SomeClass::func(int)")); QTest::newRow("prefix") << items << "KTE" << (ItemList() << items.at(0) << items.at(1)); QTest::newRow("prefix_mismatch") << items << "KTEY" << (ItemList()); QTest::newRow("prefix_colon") << items << "KTE:" << (ItemList() << items.at(0) << items.at(1)); QTest::newRow("prefix_colon_mismatch") << items << "KTE:Y" << (ItemList()); QTest::newRow("prefix_colon_mismatch2") << items << "XKTE:" << (ItemList()); QTest::newRow("prefix_two_colon") << items << "KTE::" << (ItemList() << items.at(0) << items.at(1)); QTest::newRow("prefix_two_colon_mismatch") << items << "KTE::Y" << (ItemList()); QTest::newRow("prefix_two_colon_mismatch2") << items << "XKTE::" << (ItemList()); QTest::newRow("suffix") << items << "Curs" << (ItemList() << items.at(0) << items.at(1)); QTest::newRow("suffix2") << items << "curs" << (ItemList() << items.at(0) << items.at(1)); QTest::newRow("mid") << items << "SomeClass" << (ItemList() << items.at(2)); QTest::newRow("mid_abbrev") << items << "SClass" << (ItemList() << items.at(2)); } void TestQuickOpen::testAbbreviations() { QFETCH(StringList, items); QFETCH(QString, filter); QFETCH(StringList, filtered); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filter.split('/', QString::SkipEmptyParts)); QCOMPARE(filterItems.filteredItems(), filtered); } void TestQuickOpen::testAbbreviations_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const StringList items = { QStringLiteral("/foo/bar/caz/a.h"), QStringLiteral("/KateThing/CMakeLists.txt"), QStringLiteral("/FooBar/FooBar/Footestfoo.h") }; QTest::newRow("path_segments") << items << "fbc" << StringList(); QTest::newRow("path_segment_abbrev") << items << "cmli" << StringList({ items.at(1) }); QTest::newRow("path_segment_old") << items << "kate/cmake" << StringList({ items.at(1) }); QTest::newRow("path_segment_multi_mixed") << items << "ftfoo.h" << StringList({ items.at(2) }); } void TestQuickOpen::testSorting() { QFETCH(StringList, items); QFETCH(QString, filter); QFETCH(StringList, filtered); const auto filterList = filter.split('/', QString::SkipEmptyParts); PathTestFilter filterItems; filterItems.setItems(items); filterItems.setFilter(filterList); QEXPECT_FAIL("bar7", "empty parts are skipped", Abort); if (filterItems.filteredItems() != filtered) qWarning() << filterItems.filteredItems() << filtered; QCOMPARE(filterItems.filteredItems(), filtered); // check whether sorting is stable filterItems.setFilter(filterList); QCOMPARE(filterItems.filteredItems(), filtered); } void TestQuickOpen::testSorting_data() { QTest::addColumn("items"); QTest::addColumn("filter"); QTest::addColumn("filtered"); const StringList items({ QStringLiteral("/foo/a.h"), QStringLiteral("/foo/ab.h"), QStringLiteral("/foo/bc.h"), QStringLiteral("/bar/a.h")}); { QTest::newRow("no-filter") << items << QString() << items; } { const StringList filtered = { QStringLiteral("/bar/a.h") }; QTest::newRow("bar1") << items << QStringLiteral("bar") << filtered; QTest::newRow("bar2") << items << QStringLiteral("/bar") << filtered; QTest::newRow("bar3") << items << QStringLiteral("/bar/") << filtered; QTest::newRow("bar4") << items << QStringLiteral("bar/") << filtered; QTest::newRow("bar5") << items << QStringLiteral("ar/") << filtered; QTest::newRow("bar6") << items << QStringLiteral("r/") << filtered; QTest::newRow("bar7") << items << QStringLiteral("b/") << filtered; QTest::newRow("bar8") << items << QStringLiteral("b/a") << filtered; QTest::newRow("bar9") << items << QStringLiteral("b/a.h") << filtered; QTest::newRow("bar10") << items << QStringLiteral("b/a.") << filtered; } { const StringList filtered = { QStringLiteral("/foo/a.h"), QStringLiteral("/foo/ab.h") }; QTest::newRow("foo_a1") << items << QStringLiteral("foo/a") << filtered; QTest::newRow("foo_a2") << items << QStringLiteral("/f/a") << filtered; } { // now matches ab.h too because of abbreviation matching, but should be sorted last const StringList filtered = { QStringLiteral("/foo/a.h"), QStringLiteral("/bar/a.h"), QStringLiteral("/foo/ab.h") }; QTest::newRow("a_h") << items << QStringLiteral("a.h") << filtered; } { const StringList base = { QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test_b_1"), QStringLiteral("/foo/test_b") }; const StringList sorted = { QStringLiteral("/foo/test_b"), QStringLiteral("/foo/test_b_1") }; QTest::newRow("prefer_exact") << base << QStringLiteral("test_b") << sorted; } { // from commit: 769491f06a4560a4798592ff060675ffb0d990a6 const QString file = QStringLiteral("/myProject/someStrangePath/anItem.cpp"); const StringList base = { QStringLiteral("/foo/a"), file }; const StringList filtered = { file }; QTest::newRow("strange") << base << QStringLiteral("strange/item") << filtered; } { const StringList base = { QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test_b_1"), QStringLiteral("/foo/test_b"), QStringLiteral("/foo/test/a") }; const StringList sorted = { QStringLiteral("/foo/test_b_1"), QStringLiteral("/foo/test_b"), QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test/a") }; QTest::newRow("prefer_start1") << base << QStringLiteral("test") << sorted; QTest::newRow("prefer_start2") << base << QStringLiteral("foo/test") << sorted; } { const StringList base = { QStringLiteral("/muh/kuh/asdf/foo"), QStringLiteral("/muh/kuh/foo/asdf") }; const StringList reverse = { QStringLiteral("/muh/kuh/foo/asdf"), QStringLiteral("/muh/kuh/asdf/foo") }; QTest::newRow("prefer_start3") << base << QStringLiteral("f") << base; QTest::newRow("prefer_start4") << base << QStringLiteral("/fo") << base; QTest::newRow("prefer_start5") << base << QStringLiteral("/foo") << base; QTest::newRow("prefer_start6") << base << QStringLiteral("a") << reverse; QTest::newRow("prefer_start7") << base << QStringLiteral("/a") << reverse; QTest::newRow("prefer_start8") << base << QStringLiteral("uh/as") << reverse; QTest::newRow("prefer_start9") << base << QStringLiteral("asdf") << reverse; } { QTest::newRow("duplicate") << StringList({ QStringLiteral("/muh/kuh/asdf/foo") }) << QStringLiteral("kuh/kuh") << StringList(); } { const StringList fuzzyItems = { QStringLiteral("/foo/bar.h"), QStringLiteral("/foo/fooXbar.h"), QStringLiteral("/foo/fXoXoXbXaXr.h"), QStringLiteral("/bar/FOOxBAR.h") }; QTest::newRow("fuzzy1") << fuzzyItems << QStringLiteral("br") << fuzzyItems; QTest::newRow("fuzzy2") << fuzzyItems << QStringLiteral("foo/br") << StringList({ QStringLiteral("/foo/bar.h"), QStringLiteral("/foo/fooXbar.h"), QStringLiteral("/foo/fXoXoXbXaXr.h") }); QTest::newRow("fuzzy3") << fuzzyItems << QStringLiteral("b/br") << StringList({ QStringLiteral("/bar/FOOxBAR.h") }); QTest::newRow("fuzzy4") << fuzzyItems << QStringLiteral("br/br") << StringList(); QTest::newRow("fuzzy5") << fuzzyItems << QStringLiteral("foo/bar") << StringList({ QStringLiteral("/foo/bar.h"), QStringLiteral("/foo/fooXbar.h"), QStringLiteral("/foo/fXoXoXbXaXr.h") }); QTest::newRow("fuzzy6") << fuzzyItems << QStringLiteral("foobar") << StringList({ QStringLiteral("/foo/fooXbar.h"), QStringLiteral("/foo/fXoXoXbXaXr.h"), QStringLiteral("/bar/FOOxBAR.h") }); } { const StringList a = { QStringLiteral("/home/user/src/code/user/something"), QStringLiteral("/home/user/src/code/home/else"), }; const StringList b = { QStringLiteral("/home/user/src/code/home/else"), QStringLiteral("/home/user/src/code/user/something"), }; QTest::newRow("prefer_multimatch_a_home") << a << QStringLiteral("home") << b; QTest::newRow("prefer_multimatch_b_home") << b << QStringLiteral("home") << b; QTest::newRow("prefer_multimatch_a_user") << a << QStringLiteral("user") << a; QTest::newRow("prefer_multimatch_b_user") << b << QStringLiteral("user") << a; } { const StringList a = { QStringLiteral("/home/user/project/A/file"), QStringLiteral("/home/user/project/B/project/A/file"), QStringLiteral("/home/user/project/user/C/D/E"), }; const StringList b = { QStringLiteral("/home/user/project/B/project/A/file"), QStringLiteral("/home/user/project/A/file"), }; const StringList c = { QStringLiteral("/home/user/project/user/C/D/E"), QStringLiteral("/home/user/project/A/file"), QStringLiteral("/home/user/project/B/project/A/file"), }; QTest::newRow("prefer_multimatch_a_project/file") << a << QStringLiteral("project/file") << b; QTest::newRow("prefer_multimatch_b_project/file") << b << QStringLiteral("project/file") << b; QTest::newRow("prefer_multimatch_a_project/le") << a << QStringLiteral("project/le") << b; QTest::newRow("prefer_multimatch_b_project/le") << b << QStringLiteral("project/le") << b; QTest::newRow("prefer_multimatch_a_project/a/file") << a << QStringLiteral("project/a/file") << b; QTest::newRow("prefer_multimatch_b_project/a/file") << b << QStringLiteral("project/a/file") << b; QTest::newRow("prefer_multimatch_a_project_user") << a << QStringLiteral("user") << c; QTest::newRow("prefer_multimatch_c_project_user") << c << QStringLiteral("user") << c; } } void TestQuickOpen::testStableSort() { const StringList items = { QStringLiteral("a/c/CMakeLists.txt"), QStringLiteral("a/d/CMakeLists.txt"), QStringLiteral("b/e/CMakeLists.txt"), QStringLiteral("b/f/CMakeLists.txt") }; PathTestFilter filterItems; filterItems.setItems(items); QStringList filter = {QString()}; for (auto c : QStringLiteral("CMakeLists.txt")) { filter[0].append(c); filterItems.setFilter(filter); QCOMPARE(filterItems.filteredItems(), items); } } void TestQuickOpen::testProjectFileFilter() { QTemporaryDir dir; TestProject* project = new TestProject(Path(dir.path())); ProjectFolderItem* foo = createChild(project->projectItem(), QStringLiteral("foo")); createChild(foo, QStringLiteral("bar")); createChild(foo, QStringLiteral("asdf")); createChild(foo, QStringLiteral("space bar")); ProjectFolderItem* asdf = createChild(project->projectItem(), QStringLiteral("asdf")); createChild(asdf, QStringLiteral("bar")); QTemporaryFile tmpFile; tmpFile.setFileName(dir.path() + "/aaaa"); QVERIFY(tmpFile.open()); ProjectFileItem* aaaa = new ProjectFileItem(QStringLiteral("aaaa"), project->projectItem()); QCOMPARE(project->fileSet().size(), 5); ProjectFileDataProvider provider; QCOMPARE(provider.itemCount(), 0u); projectController->addProject(project); const QStringList original = QStringList() << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar"); // lazy load QCOMPARE(provider.itemCount(), 0u); provider.reset(); QCOMPARE(items(provider), original); QCOMPARE(provider.itemPath(provider.items().first()), aaaa->path()); QCOMPARE(provider.data(0)->text(), QStringLiteral("aaaa")); // don't show opened file QVERIFY(core->documentController()->openDocument(QUrl::fromLocalFile(tmpFile.fileName()))); // lazy load again QCOMPARE(items(provider), original); provider.reset(); QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); // prefer files starting with filter provider.setFilterText(QStringLiteral("as")); qDebug() << items(provider); QCOMPARE(items(provider), QStringList() << QStringLiteral("foo/asdf") << QStringLiteral("asdf/bar")); // clear filter provider.reset(); QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); // update on document close, lazy load again core->documentController()->closeAllDocuments(); QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar")); provider.reset(); QCOMPARE(items(provider), original); ProjectFileItem* blub = createChild(project->projectItem(), QStringLiteral("blub")); // lazy load QCOMPARE(provider.itemCount(), 5u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // ensure we don't add stuff multiple times QMetaObject::invokeMethod(&provider, "fileAddedToSet", Q_ARG(KDevelop::IProject*, project), Q_ARG(KDevelop::IndexedString, blub->indexedPath())); QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 6u); // lazy load in this implementation delete blub; QCOMPARE(provider.itemCount(), 6u); provider.reset(); QCOMPARE(provider.itemCount(), 5u); QCOMPARE(items(provider), original); // allow filtering by path to project provider.setFilterText(dir.path()); QCOMPARE(items(provider), original); Path buildFolderItem(project->path().parent(), QStringLiteral(".build/generated.h")); new ProjectFileItem(project, buildFolderItem, project->projectItem()); // lazy load QCOMPARE(items(provider), original); provider.reset(); QCOMPARE(items(provider), QStringList() << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar") << QStringLiteral("../.build/generated.h")); projectController->closeProject(project); provider.reset(); QVERIFY(!provider.itemCount()); }