diff --git a/CMakeLists.txt b/CMakeLists.txt index d2574ed413..7efc6d9b96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,126 +1,134 @@ # KDevelop version set( KDEVELOP_VERSION_MAJOR 4 ) set( KDEVELOP_VERSION_MINOR 90 ) set( KDEVELOP_VERSION_PATCH 90 ) set( KDEVELOP_VERSION "${KDEVELOP_VERSION_MAJOR}.${KDEVELOP_VERSION_MINOR}.${KDEVELOP_VERSION_PATCH}" ) ################################################################################ cmake_minimum_required(VERSION 2.8.12) project(KDevelop) # we need some parts of the ECM CMake helpers find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevelop_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) include(CTest) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) set(QT_MIN_VERSION "5.4.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Widgets Concurrent Quick QuickWidgets WebKitWidgets Script Test) set(KF5_DEP_VERSION "5.3.0") find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Config Declarative DocTools IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff NotifyConfig Parts Service TextEditor ThreadWeaver XmlGui WindowSystem ) find_package(KDevelop-PG-Qt 1.90.90) set_package_properties(KDevelop-PG-Qt PROPERTIES PURPOSE "KDevelop parser generator library. Required for the QMake Builder/Manager plugin." ) find_package(KDevPlatform ${KDEVELOP_VERSION} REQUIRED) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) # Turn off missing-field-initializers warning to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html if (CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") endif() include_directories(${KDevelop_SOURCE_DIR} ${KDevelop_BINARY_DIR} ) +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) +if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug" + OR CMAKE_BUILD_TYPE_TOLOWER STREQUAL "") + set(COMPILER_OPTIMIZATIONS_DISABLED TRUE) +else() + set(COMPILER_OPTIMIZATIONS_DISABLED FALSE) +endif() + # create config.h configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) add_subdirectory(pics) add_subdirectory(app) add_subdirectory(formatters) add_subdirectory(languages) add_subdirectory(projectbuilders) add_subdirectory(projectmanagers) add_subdirectory(debuggers) add_subdirectory(app_templates) add_subdirectory(documentation) add_subdirectory(kdeintegration) add_subdirectory(utils) add_subdirectory(file_templates) add_subdirectory(providers) add_subdirectory(doc) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevelop") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDevelopConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) ecm_setup_version(${KDEVELOP_VERSION_MAJOR}.${KDEVELOP_VERSION_MINOR}.${KDEVELOP_VERSION_PATCH} VARIABLE_PREFIX KDEVELOP VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevelop_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfigVersion.cmake" SOVERSION ${KDEVELOP_LIB_SOVERSION} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kdevelop_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/kdevelop") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KDevelopConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(EXPORT KDevelopTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" NAMESPACE KDev:: FILE KDevelopTargets.cmake) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake) install(FILES kdevelop.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/appdata/) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/languages/clang/clangsettings/clangsettingsmanager.h b/languages/clang/clangsettings/clangsettingsmanager.h index 562a4166ae..379b747d15 100644 --- a/languages/clang/clangsettings/clangsettingsmanager.h +++ b/languages/clang/clangsettings/clangsettingsmanager.h @@ -1,76 +1,76 @@ /* * 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 class KConfig; 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); struct CodeCompletionSettings { bool macros = true; bool lookAhead = false; }; struct AssistantsSettings { bool forwardDeclare = true; }; class ClangSettingsManager { public: static ClangSettingsManager* self(); AssistantsSettings assistantsSettings() const; CodeCompletionSettings codeCompletionSettings() const; ParserSettings parserSettings(KDevelop::ProjectBaseItem* item) const; private: ClangSettingsManager(); bool m_enableTesting = false; - friend class TestCodeCompletion; + friend class CodeCompletionTestBase; }; #endif // CLANGSETTINGSMANAGER_H diff --git a/languages/clang/tests/CMakeLists.txt b/languages/clang/tests/CMakeLists.txt index 69149d16bf..c47744e4f5 100644 --- a/languages/clang/tests/CMakeLists.txt +++ b/languages/clang/tests/CMakeLists.txt @@ -1,85 +1,99 @@ add_executable(clang-parser clang-parser.cpp ) target_link_libraries(clang-parser KDev::Tests kdevclangduchain ) +add_library(codecompletiontestbase STATIC codecompletiontestbase.cpp) +target_link_libraries(codecompletiontestbase PUBLIC + KDev::Tests + Qt5::Test + kdevclangcodecompletion + kdevclangduchain +) + add_executable(clang-minimal-visitor WIN32 minimal_visitor.cpp ) ecm_mark_nongui_executable(clang-minimal-visitor) target_link_libraries(clang-minimal-visitor ${CLANG_LIBCLANG_LIB} ) ecm_add_test(test_buddies.cpp TEST_NAME test_buddies-clang LINK_LIBRARIES KDev::Tests Qt5::Test ) ecm_add_test(test_codecompletion.cpp TEST_NAME test_codecompletion LINK_LIBRARIES - KDev::Tests - Qt5::Test - kdevclangcodecompletion - kdevclangduchain + codecompletiontestbase ) ecm_add_test(test_assistants.cpp TEST_NAME test_assistants LINK_LIBRARIES KDev::Tests Qt5::Test kdevclangduchain kdevclangutil ) ecm_add_test(test_clangutils.cpp TEST_NAME test_clangutils LINK_LIBRARIES KDev::Tests Qt5::Test ${CLANG_LIBCLANG_LIB} kdevclangduchain ) ecm_add_test(test_duchain.cpp TEST_NAME test_duchain-clang LINK_LIBRARIES KDev::Tests Qt5::Test kdevclangduchain ) ecm_add_test(test_duchainutils.cpp TEST_NAME test_duchainutils LINK_LIBRARIES KDev::Tests Qt5::Test kdevclangcodecompletion kdevclangduchain ) ecm_add_test(test_problems.cpp TEST_NAME test_problems LINK_LIBRARIES KDev::Tests Qt5::Test kdevclangduchain ) configure_file("testfilepaths.h.cmake" "testfilepaths.h" ESCAPE_QUOTES) ecm_add_test(test_files.cpp TEST_NAME test_files-clang LINK_LIBRARIES Qt5::Test Qt5::Core KDev::Language KDev::Tests ) + +if(NOT COMPILER_OPTIMIZATIONS_DISABLED) + ecm_add_test(bench_codecompletion.cpp + TEST_NAME bench_codecompletion + LINK_LIBRARIES + codecompletiontestbase + ) + set_tests_properties(bench_codecompletion PROPERTIES TIMEOUT 30) +endif() diff --git a/languages/clang/tests/bench_codecompletion.cpp b/languages/clang/tests/bench_codecompletion.cpp new file mode 100644 index 0000000000..ec8a591da9 --- /dev/null +++ b/languages/clang/tests/bench_codecompletion.cpp @@ -0,0 +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); + +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/languages/clang/tests/bench_codecompletion.h b/languages/clang/tests/bench_codecompletion.h new file mode 100644 index 0000000000..645a1937ba --- /dev/null +++ b/languages/clang/tests/bench_codecompletion.h @@ -0,0 +1,46 @@ +/* + * 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 BENCHCODECOMPLETION_H +#define BENCHCODECOMPLETION_H + +#include "codecompletiontestbase.h" + +class ClangIndex; +class ClangCodeCompletionModel; + +class BenchCodeCompletion : public CodeCompletionTestBase +{ + Q_OBJECT + +public: + BenchCodeCompletion(); + ~BenchCodeCompletion(); + +private slots: + void benchCodeCompletion_data(); + void benchCodeCompletion(); + +private: + QScopedPointer m_index; + ClangCodeCompletionModel* m_model; +}; + +#endif // BENCHCODECOMPLETION_H diff --git a/languages/clang/tests/codecompletiontestbase.cpp b/languages/clang/tests/codecompletiontestbase.cpp new file mode 100644 index 0000000000..7cd3a72453 --- /dev/null +++ b/languages/clang/tests/codecompletiontestbase.cpp @@ -0,0 +1,76 @@ +/* + * 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 "codecompletiontestbase.h" + +#include +#include + +#include "clangsettings/clangsettingsmanager.h" + +#include + +#include +#include +#include + +#include +#if KTEXTEDITOR_VERSION < QT_VERSION_CHECK(5, 10, 0) +Q_DECLARE_METATYPE(KTextEditor::Cursor); +#endif + +void DeleteDocument::operator()(KTextEditor::View* view) const +{ + delete view->document(); +} + +std::unique_ptr CodeCompletionTestBase::createView(const QUrl& url, QObject* parent) const +{ + KTextEditor::Editor* editor = KTextEditor::Editor::instance(); + Q_ASSERT(editor); + + auto doc = editor->createDocument(parent); + Q_ASSERT(doc); + bool opened = doc->openUrl(url); + Q_ASSERT(opened); + Q_UNUSED(opened); + + auto view = doc->createView(nullptr); + Q_ASSERT(view); + return std::unique_ptr(view); +} + +void CodeCompletionTestBase::initTestCase() +{ + QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); + QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); + AutoTestShell::init({QStringLiteral("kdevclangsupport")}); + TestCore::initialize(); + + ClangSettingsManager::self()->m_enableTesting = true; +} + +void CodeCompletionTestBase::cleanupTestCase() +{ + TestCore::shutdown(); +} diff --git a/languages/clang/tests/test_codecompletion.h b/languages/clang/tests/codecompletiontestbase.h similarity index 51% copy from languages/clang/tests/test_codecompletion.h copy to languages/clang/tests/codecompletiontestbase.h index 5c49135a8f..3b4da7d6f6 100644 --- a/languages/clang/tests/test_codecompletion.h +++ b/languages/clang/tests/codecompletiontestbase.h @@ -1,63 +1,51 @@ /* * 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 TESTCODECOMPLETION_H -#define TESTCODECOMPLETION_H +#ifndef CODECOMPLETIONTESTBASE_H +#define CODECOMPLETIONTESTBASE_H #include -class TestCodeCompletion : public QObject +#include + +namespace KTextEditor { +class View; +} + +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 slots: void initTestCase(); void cleanupTestCase(); - - void testIncludePathCompletion_data(); - void testIncludePathCompletion(); - void testIncludePathCompletionLocal(); - - void testClangCodeCompletion(); - void testClangCodeCompletion_data(); - void testVirtualOverride(); - void testVirtualOverride_data(); - void testImplement(); - void testImplement_data(); - void testImplementOtherFile(); - void testInvalidCompletions(); - void testInvalidCompletions_data(); - void testCompletionPriority(); - void testCompletionPriority_data(); - void testReplaceMemberAccess(); - void testReplaceMemberAccess_data(); - void testArgumentHintCompletion(); - void testArgumentHintCompletion_data(); - - void testOverloadedFunctions(); - void testVariableScope(); - void testArgumentHintCompletionDefaultParameters(); - - void testCompleteFunction_data(); - void testCompleteFunction(); }; -#endif // TESTCODECOMPLETION_H +#endif // CODECOMPLETIONTESTBASE_H diff --git a/languages/clang/tests/test_codecompletion.cpp b/languages/clang/tests/test_codecompletion.cpp index c7a2a265bc..45f13b2707 100644 --- a/languages/clang/tests/test_codecompletion.cpp +++ b/languages/clang/tests/test_codecompletion.cpp @@ -1,1167 +1,1123 @@ /* * 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 "duchain/parsesession.h" #include "util/clangtypes.h" #include #include #include #include #include #include "codecompletion/completionhelper.h" #include "codecompletion/context.h" #include "codecompletion/includepathcompletioncontext.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include -#include -#if KTEXTEDITOR_VERSION < QT_VERSION_CHECK(5, 10, 0) -Q_DECLARE_METATYPE(KTextEditor::Cursor); -#endif - 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); 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); -void TestCodeCompletion::initTestCase() -{ - QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); - QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); - AutoTestShell::init({QStringLiteral("kdevclangsupport")}); - TestCore::initialize(); - - ClangSettingsManager::self()->m_enableTesting = true; -} - -void TestCodeCompletion::cleanupTestCase() -{ - TestCore::shutdown(); -} - namespace { struct NoopTestFunction { void operator()(const ClangCodeCompletionItemTester& /*tester*/) const { } }; 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 = new ClangCodeCompletionContext(DUContextPointer(top), sessionData, top->url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(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); 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); 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, "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, "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); DUContextPointer topPtr(top); // don't hold DUChain lock when constructing ClangCodeCompletionContext lock.unlock(); auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(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, "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); DUContextPointer topPtr(top); lock.unlock(); auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, code); QApplication::processEvents(); document->close(KDevelop::IDocument::Silent); // The previous ClangCodeCompletionContext call should replace member access. context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(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" }}; } 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) overriden;\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("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"}}; } 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()"}}; // FIXME: the rage seems to be wrong here (stops after =), but I don't know how to fix that // QDEBUG : TestCodeCompletion::testImplement(deleted-overload-global) default: "int x(int)=" contains "=\\s*delete\\s*;" = false 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()"}}; } void TestCodeCompletion::testImplementOtherFile() { TestFile header1("void foo();", "h"); QVERIFY(header1.parseAndWait()); TestFile header2("void bar();", "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()), "cpp", &header1); CompletionItems expectedItems{{3,1}, {"asdf()", "foo()"}}; QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(impl.topContext(), expectedItems); } 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") << QString("#include ") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-2") << QString("#include <") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-3") << QString("#include <") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-4") << QString("# include <") << KTextEditor::Cursor(0, 12) << QString("iostream") << QString("# include "); QTest::newRow("global-5") << QString("# include <") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include "); QTest::newRow("global-6") << QString("# include <> /* 1 */") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include /* 1 */"); QTest::newRow("global-7") << QString("# include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 21) << QString("iostream") << QString("# include /* 1 */ /* 1 */"); QTest::newRow("global-8") << QString("# /* 1 */ include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 28) << QString("iostream") << QString("# /* 1 */ include /* 1 */ /* 1 */"); QTest::newRow("global-9") << QString("#include ") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-10") << QString("#include ") << KTextEditor::Cursor(0, 14) << QString("cstdint") << QString("#include "); QTest::newRow("global-11") << QString("#include ") << KTextEditor::Cursor(0, 17) << QString("cstdint") << QString("#include "); QTest::newRow("local-0") << QString("#include \"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-1") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-2") << QString("#include \"foo/") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-3") << QString("# /* 1 */ include /* 1 */ \"\" /* 1 */") << KTextEditor::Cursor(0, 28) << QString("foo/") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */"); QTest::newRow("local-4") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */") << KTextEditor::Cursor(0, 31) << QString("bar/") << QString("# /* 1 */ include /* 1 */ \"foo/bar/\" /* 1 */"); QTest::newRow("local-5") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-6") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-7") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); } -struct DeleteDocument -{ - void operator()(KTextEditor::View* view) const - { - delete view->document(); - } -}; - -static std::unique_ptr createView(const QUrl& url, QObject* parent) -{ - KTextEditor::Editor* editor = KTextEditor::Editor::instance(); - Q_ASSERT(editor); - - auto doc = editor->createDocument(parent); - Q_ASSERT(doc); - bool opened = doc->openUrl(url); - Q_ASSERT(opened); - Q_UNUSED(opened); - - auto view = doc->createView(nullptr); - Q_ASSERT(view); - return std::unique_ptr(view); -} - 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, "cpp", 0, tempDir.path()); 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); qDebug() << view.get(); 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("int foo() { return 42; }\n", "h"); TestFile impl("#include \"", "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("void f(); int f(int); void f(int, double){\n ", "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); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(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("int var; \nvoid test(int var) {int tmp =\n }", "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); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {2, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QCOMPARE(tester.items.size(), 4); auto item = tester.findItem(QStringLiteral("var")); VERIFY(item); QCOMPARE(item->declaration()->range().start, CursorInRevision(1, 14)); } void TestCodeCompletion::testArgumentHintCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testArgumentHintCompletion_data() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("global function") << "void foo(int);\n" "int main() { \nfoo( " << CompletionItems{{2,4}, { "foo", "foo", "main" }}; QTest::newRow("member function") << "struct Struct{ void foo(int);}\n" "int main() {Struct s; \ns.foo( " << CompletionItems{{2,6}, { "Struct", "foo", "main", "s" }}; QTest::newRow("template function") << "template void foo(T);\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "main" }}; QTest::newRow("overloaded functions") << "void foo(int); void foo(int, double)\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "foo", "foo", "main" }}; QTest::newRow("overloaded functions2") << "void foo(int); void foo(int, double)\n" "int main() { foo(1,\n " << CompletionItems{{2,1}, { "foo", "foo", "foo", "main" }}; } void TestCodeCompletion::testArgumentHintCompletionDefaultParameters() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif TestFile file("void f(int i, int j = 0, double k =1){\nf( ", "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); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 2}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(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(QStringLiteral("f(int i, int j, double k)"), itemDisplay); } 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}"; } diff --git a/languages/clang/tests/test_codecompletion.h b/languages/clang/tests/test_codecompletion.h index 5c49135a8f..d7ea1bc5b5 100644 --- a/languages/clang/tests/test_codecompletion.h +++ b/languages/clang/tests/test_codecompletion.h @@ -1,63 +1,60 @@ /* * Copyright 2014 David Stevens * 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 TESTCODECOMPLETION_H #define TESTCODECOMPLETION_H -#include +#include "codecompletiontestbase.h" -class TestCodeCompletion : public QObject +class TestCodeCompletion : public CodeCompletionTestBase { Q_OBJECT private slots: - void initTestCase(); - void cleanupTestCase(); - void testIncludePathCompletion_data(); void testIncludePathCompletion(); void testIncludePathCompletionLocal(); void testClangCodeCompletion(); void testClangCodeCompletion_data(); void testVirtualOverride(); void testVirtualOverride_data(); void testImplement(); void testImplement_data(); void testImplementOtherFile(); void testInvalidCompletions(); void testInvalidCompletions_data(); void testCompletionPriority(); void testCompletionPriority_data(); void testReplaceMemberAccess(); void testReplaceMemberAccess_data(); void testArgumentHintCompletion(); void testArgumentHintCompletion_data(); void testOverloadedFunctions(); void testVariableScope(); void testArgumentHintCompletionDefaultParameters(); void testCompleteFunction_data(); void testCompleteFunction(); }; #endif // TESTCODECOMPLETION_H