/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 static bool waitForSignalOrTimeout( const typename QtPrivate::FunctionPointer::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 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 headerEditor(EditorManager::openEditor(headerPath)); QVERIFY(headerEditor); const auto headerDoc = qobject_cast(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 cppFileEditor(EditorManager::openEditor(cppFilePath)); QVERIFY(cppFileEditor); const auto cppDoc = qobject_cast(cppFileEditor->document()); QVERIFY(cppDoc); QVERIFY(client->documentForFilePath(cppFilePath) == cppDoc); // ... and we're ready to go. QList searchResults; connect(client, &ClangdClient::foundReferences, this, [&searchResults](const QList &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