summaryrefslogtreecommitdiff
path: root/src/plugins/clangcodemodel/test/clangdtests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/clangcodemodel/test/clangdtests.cpp')
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.cpp331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp
new file mode 100644
index 0000000000..0145f79cdb
--- /dev/null
+++ b/src/plugins/clangcodemodel/test/clangdtests.cpp
@@ -0,0 +1,331 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "clangdtests.h"
+
+#include "clangautomationutils.h"
+#include "clangbatchfileprocessor.h"
+#include "../clangdclient.h"
+#include "../clangmodelmanagersupport.h"
+
+#include <cplusplus/FindUsages.h>
+#include <cpptools/cppcodemodelsettings.h>
+#include <cpptools/cpptoolsreuse.h>
+#include <cpptools/cpptoolstestcase.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/find/searchresultitem.h>
+#include <projectexplorer/kitmanager.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorer.h>
+#include <qtsupport/qtkitinformation.h>
+#include <utils/algorithm.h>
+
+#include <QEventLoop>
+#include <QScopedPointer>
+#include <QTimer>
+#include <QtTest>
+
+using namespace CPlusPlus;
+using namespace Core;
+using namespace ProjectExplorer;
+
+namespace ClangCodeModel {
+namespace Internal {
+namespace Tests {
+
+void ClangdTests::initTestCase()
+{
+ const auto settings = CppTools::codeModelSettings();
+ const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD");
+ if (!clangdFromEnv.isEmpty())
+ settings->setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv));
+ const auto clangd = settings->clangdFilePath();
+ if (clangd.isEmpty() || !clangd.exists())
+ QSKIP("clangd binary not found");
+ settings->setUseClangd(true);
+}
+
+template <typename Signal> static bool waitForSignalOrTimeout(
+ const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal)
+{
+ QTimer timer;
+ timer.setSingleShot(true);
+ timer.setInterval(timeOutInMs());
+ QEventLoop loop;
+ QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
+ QObject::connect(sender, signal, &loop, &QEventLoop::quit);
+ timer.start();
+ loop.exec();
+ return timer.isActive();
+}
+
+// The main point here is to test our access type categorization.
+// We do not try to stress-test clangd's "Find References" functionality; such tests belong
+// into LLVM.
+void ClangdTests::testFindReferences()
+{
+ // Find suitable kit.
+ const QList<Kit *> allKits = KitManager::kits();
+ if (allKits.isEmpty())
+ QSKIP("This test requires at least one kit to be present");
+ Kit * const kit = Utils::findOr(allKits, nullptr, [](const Kit *k) {
+ return k->isValid() && QtSupport::QtKitAspect::qtVersion(k);
+ });
+ if (!kit)
+ QSKIP("The test requires at least one valid kit with a valid Qt");
+
+ // Copy project out of qrc file, open it, and set up target.
+ CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("find-usages"));
+ QVERIFY(testDir.isValid());
+ const auto openProjectResult = ProjectExplorerPlugin::openProject(
+ testDir.absolutePath("find-usages.pro"));
+ QVERIFY2(openProjectResult, qPrintable(openProjectResult.errorMessage()));
+ openProjectResult.project()->configureAsExampleProject(kit);
+
+ // Setting up the project should result in a clangd client being created.
+ // Wait until that has happened.
+ const auto modelManagerSupport = ClangModelManagerSupport::instance();
+ ClangdClient *client = modelManagerSupport->clientForProject(openProjectResult.project());
+ if (!client) {
+ QVERIFY(waitForSignalOrTimeout(modelManagerSupport,
+ &ClangModelManagerSupport::createdClient));
+ client = modelManagerSupport->clientForProject(openProjectResult.project());
+ }
+ QVERIFY(client);
+ client->enableTesting();
+
+ // Wait until the client is fully initialized, i.e. it's completed the handshake
+ // with the server.
+ if (!client->reachable())
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::initialized));
+ QVERIFY(client->reachable());
+
+ // The kind of AST support we need was introduced in LLVM 13.
+ if (client->versionNumber() < QVersionNumber(13))
+ QSKIP("Find Usages test needs clang >= 13");
+
+ // Wait for index to build.
+ if (!client->isFullyIndexed())
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::indexingFinished));
+ QVERIFY(client->isFullyIndexed());
+
+ // Open cpp documents.
+ struct EditorCloser {
+ static void cleanup(IEditor *editor) { EditorManager::closeEditors({editor}); }
+ };
+ const auto headerPath = Utils::FilePath::fromString(testDir.absolutePath("defs.h"));
+ QVERIFY2(headerPath.exists(), qPrintable(headerPath.toUserOutput()));
+ QScopedPointer<IEditor, EditorCloser> headerEditor(
+ EditorManager::openEditor(headerPath.toString()));
+ QVERIFY(headerEditor);
+ const auto headerDoc = qobject_cast<TextEditor::TextDocument *>(headerEditor->document());
+ QVERIFY(headerDoc);
+ QVERIFY(client->documentForFilePath(headerPath) == headerDoc);
+ const auto cppFilePath = Utils::FilePath::fromString(testDir.absolutePath("main.cpp"));
+ QVERIFY2(cppFilePath.exists(), qPrintable(cppFilePath.toUserOutput()));
+ QScopedPointer<IEditor, EditorCloser> cppFileEditor(
+ EditorManager::openEditor(cppFilePath.toString()));
+ QVERIFY(cppFileEditor);
+ const auto cppDoc = qobject_cast<TextEditor::TextDocument *>(cppFileEditor->document());
+ QVERIFY(cppDoc);
+ QVERIFY(client->documentForFilePath(cppFilePath) == cppDoc);
+
+ // ... and we're ready to go.
+ QList<SearchResultItem> searchResults;
+ connect(client, &ClangdClient::foundReferences, this,
+ [&searchResults](const QList<SearchResultItem> &results) {
+ if (results.isEmpty())
+ return;
+ if (results.first().path().first().endsWith("defs.h"))
+ searchResults = results + searchResults; // Guarantee expected file order.
+ else
+ searchResults += results;
+ });
+
+#define FIND_USAGES(doc, pos) do { \
+ QTextCursor cursor((doc)->document()); \
+ cursor.setPosition((pos)); \
+ searchResults.clear(); \
+ client->findUsages((doc), cursor); \
+ QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::findUsagesDone)); \
+} while (false)
+
+#define EXPECT_RESULT(index, lne, col, type) \
+ QCOMPARE(searchResults.at(index).mainRange().begin.line, lne); \
+ QCOMPARE(searchResults.at(index).mainRange().begin.column, col); \
+ QCOMPARE(searchResults.at(index).userData().toInt(), int(type))
+
+ // All kinds of checks involving a struct member.
+ FIND_USAGES(headerDoc, 55);
+ QCOMPARE(searchResults.size(), 32);
+ EXPECT_RESULT(0, 2, 17, Usage::Type::Read);
+ EXPECT_RESULT(1, 3, 15, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 6, 17, Usage::Type::WritableRef);
+ EXPECT_RESULT(3, 8, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 9, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(5, 10, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(6, 11, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 12, 14, Usage::Type::WritableRef);
+ EXPECT_RESULT(8, 13, 26, Usage::Type::WritableRef);
+ EXPECT_RESULT(9, 14, 23, Usage::Type::Read);
+ EXPECT_RESULT(10, 15, 14, Usage::Type::Read);
+ EXPECT_RESULT(11, 16, 24, Usage::Type::WritableRef);
+ EXPECT_RESULT(12, 17, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(13, 18, 22, Usage::Type::Read);
+ EXPECT_RESULT(14, 19, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(15, 20, 12, Usage::Type::Read);
+ EXPECT_RESULT(16, 21, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(17, 22, 13, Usage::Type::Read);
+ EXPECT_RESULT(18, 23, 12, Usage::Type::Read);
+ EXPECT_RESULT(19, 42, 20, Usage::Type::Read);
+ EXPECT_RESULT(20, 44, 15, Usage::Type::Read);
+ EXPECT_RESULT(21, 47, 15, Usage::Type::Write);
+ EXPECT_RESULT(22, 50, 11, Usage::Type::Read);
+ EXPECT_RESULT(23, 51, 11, Usage::Type::Write);
+ EXPECT_RESULT(24, 52, 9, Usage::Type::Write);
+ EXPECT_RESULT(25, 53, 7, Usage::Type::Write);
+ EXPECT_RESULT(26, 56, 7, Usage::Type::Write);
+ EXPECT_RESULT(27, 56, 25, Usage::Type::Other);
+ EXPECT_RESULT(28, 58, 13, Usage::Type::Read);
+ EXPECT_RESULT(29, 58, 25, Usage::Type::Read);
+ EXPECT_RESULT(30, 59, 7, Usage::Type::Write);
+ EXPECT_RESULT(31, 59, 24, Usage::Type::Read);
+
+ // Detect constructor member initialization as a write access.
+ FIND_USAGES(headerDoc, 68);
+ QCOMPARE(searchResults.size(), 2);
+ EXPECT_RESULT(0, 2, 10, Usage::Type::Write);
+ EXPECT_RESULT(1, 4, 8, Usage::Type::Declaration);
+
+ // Detect direct member initialization.
+ FIND_USAGES(headerDoc, 101);
+ QCOMPARE(searchResults.size(), 2);
+ EXPECT_RESULT(0, 5, 21, Usage::Type::Initialization);
+ EXPECT_RESULT(1, 45, 16, Usage::Type::Read);
+
+ // Make sure that pure virtual declaration is not mistaken for an assignment.
+ FIND_USAGES(headerDoc, 420);
+ QCOMPARE(searchResults.size(), 3); // FIXME: The override gets reported twice. clang bug?
+ EXPECT_RESULT(0, 17, 17, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 21, 9, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 21, 9, Usage::Type::Declaration);
+
+ // References to pointer variable.
+ FIND_USAGES(cppDoc, 52);
+ QCOMPARE(searchResults.size(), 11);
+ EXPECT_RESULT(0, 6, 10, Usage::Type::Initialization);
+ EXPECT_RESULT(1, 8, 4, Usage::Type::Write);
+ EXPECT_RESULT(2, 10, 4, Usage::Type::Write);
+ EXPECT_RESULT(3, 24, 5, Usage::Type::Write);
+ EXPECT_RESULT(4, 25, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(5, 26, 11, Usage::Type::Read);
+ EXPECT_RESULT(6, 27, 10, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 28, 10, Usage::Type::Read);
+ EXPECT_RESULT(8, 29, 11, Usage::Type::Read);
+ EXPECT_RESULT(9, 30, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(10, 31, 22, Usage::Type::Read);
+
+ // References to struct variable, directly and via members.
+ FIND_USAGES(cppDoc, 39);
+ QCOMPARE(searchResults.size(), 34);
+ EXPECT_RESULT(0, 5, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 6, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(2, 8, 9, Usage::Type::WritableRef);
+ EXPECT_RESULT(3, 9, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 11, 4, Usage::Type::Write);
+ EXPECT_RESULT(5, 11, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(6, 12, 12, Usage::Type::WritableRef);
+ EXPECT_RESULT(7, 13, 6, Usage::Type::Write);
+ EXPECT_RESULT(8, 14, 21, Usage::Type::Read);
+ EXPECT_RESULT(9, 15, 4, Usage::Type::Write);
+ EXPECT_RESULT(10, 15, 12, Usage::Type::Read);
+ EXPECT_RESULT(11, 16, 22, Usage::Type::WritableRef);
+ EXPECT_RESULT(12, 17, 13, Usage::Type::WritableRef);
+ EXPECT_RESULT(13, 18, 20, Usage::Type::Read);
+ EXPECT_RESULT(14, 19, 10, Usage::Type::WritableRef);
+ EXPECT_RESULT(15, 20, 10, Usage::Type::Read);
+ EXPECT_RESULT(16, 21, 11, Usage::Type::WritableRef);
+ EXPECT_RESULT(17, 22, 11, Usage::Type::Read);
+ EXPECT_RESULT(18, 23, 10, Usage::Type::Read);
+ EXPECT_RESULT(19, 32, 4, Usage::Type::Write);
+ EXPECT_RESULT(20, 33, 23, Usage::Type::WritableRef);
+ EXPECT_RESULT(21, 34, 23, Usage::Type::Read);
+ EXPECT_RESULT(22, 35, 15, Usage::Type::WritableRef);
+ EXPECT_RESULT(23, 36, 22, Usage::Type::WritableRef);
+ EXPECT_RESULT(24, 37, 4, Usage::Type::Read);
+ EXPECT_RESULT(25, 38, 4, Usage::Type::WritableRef);
+ EXPECT_RESULT(26, 39, 6, Usage::Type::WritableRef);
+ EXPECT_RESULT(27, 40, 4, Usage::Type::Read);
+ EXPECT_RESULT(28, 41, 4, Usage::Type::WritableRef);
+ EXPECT_RESULT(29, 42, 4, Usage::Type::Read);
+ EXPECT_RESULT(30, 42, 18, Usage::Type::Read);
+ EXPECT_RESULT(31, 43, 11, Usage::Type::Write);
+ EXPECT_RESULT(32, 54, 4, Usage::Type::Other);
+ EXPECT_RESULT(33, 55, 4, Usage::Type::Other);
+
+ // References to struct type.
+ FIND_USAGES(headerDoc, 7);
+ QCOMPARE(searchResults.size(), 18);
+ EXPECT_RESULT(0, 1, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 2, 4, Usage::Type::Declaration);
+ EXPECT_RESULT(2, 20, 19, Usage::Type::Other);
+
+ // These are conceptually questionable, as S is a type and thus we cannot "read from"
+ // or "write to" it. But it probably matches the intuitive user expectation.
+ EXPECT_RESULT(3, 10, 9, Usage::Type::WritableRef);
+ EXPECT_RESULT(4, 12, 4, Usage::Type::Write);
+ EXPECT_RESULT(5, 44, 12, Usage::Type::Read);
+ EXPECT_RESULT(6, 45, 13, Usage::Type::Read);
+ EXPECT_RESULT(7, 47, 12, Usage::Type::Write);
+ EXPECT_RESULT(8, 50, 8, Usage::Type::Read);
+ EXPECT_RESULT(9, 51, 8, Usage::Type::Write);
+ EXPECT_RESULT(10, 52, 6, Usage::Type::Write);
+ EXPECT_RESULT(11, 53, 4, Usage::Type::Write);
+ EXPECT_RESULT(12, 56, 4, Usage::Type::Write);
+ EXPECT_RESULT(13, 56, 22, Usage::Type::Other);
+ EXPECT_RESULT(14, 58, 10, Usage::Type::Read);
+ EXPECT_RESULT(15, 58, 22, Usage::Type::Read);
+ EXPECT_RESULT(16, 59, 4, Usage::Type::Write);
+ EXPECT_RESULT(17, 59, 21, Usage::Type::Read);
+
+ // References to subclass type.
+ FIND_USAGES(headerDoc, 450);
+ QCOMPARE(searchResults.size(), 4);
+ EXPECT_RESULT(0, 20, 7, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 5, 4, Usage::Type::Other);
+ EXPECT_RESULT(2, 13, 21, Usage::Type::Other);
+ EXPECT_RESULT(3, 32, 8, Usage::Type::Other);
+
+ // References to array variables.
+ FIND_USAGES(cppDoc, 1134);
+ QCOMPARE(searchResults.size(), 3);
+ EXPECT_RESULT(0, 57, 8, Usage::Type::Declaration);
+ EXPECT_RESULT(1, 58, 4, Usage::Type::Write);
+ EXPECT_RESULT(2, 59, 15, Usage::Type::Read);
+}
+
+} // namespace Tests
+} // namespace Internal
+} // namespace ClangCodeModel