ClangCodeModel: Add test for "follow symbol"

These are the tests from clangbackend, so we are now up to par coverage-
wise.

Change-Id: I7b8a63109bff17745782a646f684fd795f732672
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-05-25 17:27:32 +02:00
parent 170ec9eca5
commit 898558508d
11 changed files with 700 additions and 236 deletions

View File

@@ -205,7 +205,8 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
{ {
return { return {
new Tests::ClangCodeCompletionTest, new Tests::ClangCodeCompletionTest,
new Tests::ClangdTests, new Tests::ClangdTestFindReferences,
new Tests::ClangdTestFollowSymbol,
}; };
} }
#endif #endif

View File

@@ -897,7 +897,6 @@ void ClangdClient::followSymbol(
void ClangdClient::Private::handleGotoDefinitionResult() void ClangdClient::Private::handleGotoDefinitionResult()
{ {
QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return); QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return);
QTC_ASSERT(followSymbolData->cursorNode.isValid(), return);
qCDebug(clangdLog) << "handling go to definition result"; qCDebug(clangdLog) << "handling go to definition result";

View File

@@ -78,7 +78,7 @@ public:
QString error; QString error;
}; };
enum CompilationDbPurpose { Project, CodeModel }; enum class CompilationDbPurpose { Project, CodeModel };
GenerateCompilationDbResult generateCompilationDB(CppTools::ProjectInfo projectInfo, GenerateCompilationDbResult generateCompilationDB(CppTools::ProjectInfo projectInfo,
CompilationDbPurpose purpose); CompilationDbPurpose purpose);

View File

@@ -35,7 +35,6 @@
#include <cpptools/cpptoolsreuse.h> #include <cpptools/cpptoolsreuse.h>
#include <cpptools/cpptoolstestcase.h> #include <cpptools/cpptoolstestcase.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/searchresultitem.h>
#include <projectexplorer/kitmanager.h> #include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h> #include <projectexplorer/projectexplorer.h>
@@ -43,6 +42,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <QEventLoop> #include <QEventLoop>
#include <QFileInfo>
#include <QScopedPointer> #include <QScopedPointer>
#include <QTimer> #include <QTimer>
#include <QtTest> #include <QtTest>
@@ -56,7 +56,19 @@ namespace ClangCodeModel {
namespace Internal { namespace Internal {
namespace Tests { namespace Tests {
void ClangdTests::initTestCase() ClangdTest::~ClangdTest()
{
if (m_project)
ProjectExplorerPlugin::unloadProject(m_project);
delete m_projectDir;
}
Utils::FilePath ClangdTest::filePath(const QString &fileName) const
{
return Utils::FilePath::fromString(m_projectDir->absolutePath(fileName.toLocal8Bit()));
}
void ClangdTest::initTestCase()
{ {
const auto settings = CppTools::codeModelSettings(); const auto settings = CppTools::codeModelSettings();
const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD"); const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD");
@@ -66,249 +78,284 @@ void ClangdTests::initTestCase()
if (clangd.isEmpty() || !clangd.exists()) if (clangd.isEmpty() || !clangd.exists())
QSKIP("clangd binary not found"); QSKIP("clangd binary not found");
settings->setUseClangd(true); settings->setUseClangd(true);
// Find suitable kit.
m_kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
return k->isValid() && QtSupport::QtKitAspect::qtVersion(k);
});
if (!m_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.
m_projectDir = new TemporaryCopiedDir(qrcPath(QFileInfo(m_projectFileName)
.baseName().toLocal8Bit()));
QVERIFY(m_projectDir->isValid());
const auto openProjectResult = ProjectExplorerPlugin::openProject(
m_projectDir->absolutePath(m_projectFileName.toUtf8()));
QVERIFY2(openProjectResult, qPrintable(openProjectResult.errorMessage()));
m_project = openProjectResult.project();
m_project->configureAsExampleProject(m_kit);
// Setting up the project should result in a clangd client being created.
// Wait until that has happened.
const auto modelManagerSupport = ClangModelManagerSupport::instance();
m_client = modelManagerSupport->clientForProject(openProjectResult.project());
if (!m_client) {
QVERIFY(waitForSignalOrTimeout(modelManagerSupport,
&ClangModelManagerSupport::createdClient, timeOutInMs()));
m_client = modelManagerSupport->clientForProject(m_project);
}
QVERIFY(m_client);
m_client->enableTesting();
// Wait until the client is fully initialized, i.e. it's completed the handshake
// with the server.
if (!m_client->reachable())
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::initialized, timeOutInMs()));
QVERIFY(m_client->reachable());
// The kind of AST support we need was introduced in LLVM 13.
if (m_minVersion != -1 && m_client->versionNumber() < QVersionNumber(m_minVersion))
QSKIP("clangd is too old");
// Wait for index to build.
if (!m_client->isFullyIndexed())
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::indexingFinished, timeOutInMs()));
QVERIFY(m_client->isFullyIndexed());
// Open cpp documents.
for (const QString &sourceFileName : qAsConst(m_sourceFileNames)) {
const auto sourceFilePath = Utils::FilePath::fromString(
m_projectDir->absolutePath(sourceFileName.toLocal8Bit()));
QVERIFY2(sourceFilePath.exists(), qPrintable(sourceFilePath.toUserOutput()));
IEditor * const editor = EditorManager::openEditor(sourceFilePath);
QVERIFY(editor);
const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
QVERIFY(doc);
QVERIFY(m_client->documentForFilePath(sourceFilePath) == doc);
m_sourceDocuments.insert(sourceFileName, doc);
}
}
ClangdTestFindReferences::ClangdTestFindReferences()
{
setProjectFileName("find-usages.pro");
setSourceFileNames({"defs.h", "main.cpp"});
setMinimumVersion(13);
}
void ClangdTestFindReferences::initTestCase()
{
ClangdTest::initTestCase();
connect(client(), &ClangdClient::foundReferences, this,
[this](const QList<SearchResultItem> &results) {
if (results.isEmpty())
return;
if (results.first().path().first().endsWith("defs.h"))
m_actualResults = results + m_actualResults; // Guarantee expected file order.
else
m_actualResults += results;
});
}
void ClangdTestFindReferences::test_data()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<int>("pos");
using ItemList = QList<SearchResultItem>;
QTest::addColumn<ItemList>("expectedResults");
static const auto makeItem = [](int line, int column, Usage::Type type) {
SearchResultItem item;
item.setMainRange(line, column, 0);
item.setUserData(int(type));
return item;
};
QTest::newRow("struct member") << "defs.h" << 55 << ItemList{
makeItem(2, 17, Usage::Type::Read), makeItem(3, 15, Usage::Type::Declaration),
makeItem(6, 17, Usage::Type::WritableRef), makeItem(8, 11, Usage::Type::WritableRef),
makeItem(9, 13, Usage::Type::WritableRef), makeItem(10, 12, Usage::Type::WritableRef),
makeItem(11, 13, Usage::Type::WritableRef), makeItem(12, 14, Usage::Type::WritableRef),
makeItem(13, 26, Usage::Type::WritableRef), makeItem(14, 23, Usage::Type::Read),
makeItem(15, 14, Usage::Type::Read), makeItem(16, 24, Usage::Type::WritableRef),
makeItem(17, 15, Usage::Type::WritableRef), makeItem(18, 22, Usage::Type::Read),
makeItem(19, 12, Usage::Type::WritableRef), makeItem(20, 12, Usage::Type::Read),
makeItem(21, 13, Usage::Type::WritableRef), makeItem(22, 13, Usage::Type::Read),
makeItem(23, 12, Usage::Type::Read), makeItem(42, 20, Usage::Type::Read),
makeItem(44, 15, Usage::Type::Read), makeItem(47, 15, Usage::Type::Write),
makeItem(50, 11, Usage::Type::Read), makeItem(51, 11, Usage::Type::Write),
makeItem(52, 9, Usage::Type::Write), makeItem(53, 7, Usage::Type::Write),
makeItem(56, 7, Usage::Type::Write), makeItem(56, 25, Usage::Type::Other),
makeItem(58, 13, Usage::Type::Read), makeItem(58, 25, Usage::Type::Read),
makeItem(59, 7, Usage::Type::Write), makeItem(59, 24, Usage::Type::Read)};
QTest::newRow("constructor member initialization") << "defs.h" << 68 << ItemList{
makeItem(2, 10, Usage::Type::Write), makeItem(4, 8, Usage::Type::Declaration)};
QTest::newRow("direct member initialization") << "defs.h" << 101 << ItemList{
makeItem(5, 21, Usage::Type::Initialization), makeItem(45, 16, Usage::Type::Read)};
// FIXME: The override gets reported twice. clangd bug?
QTest::newRow("pure virtual declaration") << "defs.h" << 420 << ItemList{
makeItem(17, 17, Usage::Type::Declaration), makeItem(21, 9, Usage::Type::Declaration),
makeItem(21, 9, Usage::Type::Declaration)};
QTest::newRow("pointer variable") << "main.cpp" << 52 << ItemList{
makeItem(6, 10, Usage::Type::Initialization), makeItem(8, 4, Usage::Type::Write),
makeItem(10, 4, Usage::Type::Write), makeItem(24, 5, Usage::Type::Write),
makeItem(25, 11, Usage::Type::WritableRef), makeItem(26, 11, Usage::Type::Read),
makeItem(27, 10, Usage::Type::WritableRef), makeItem(28, 10, Usage::Type::Read),
makeItem(29, 11, Usage::Type::Read), makeItem(30, 15, Usage::Type::WritableRef),
makeItem(31, 22, Usage::Type::Read)};
QTest::newRow("struct variable") << "main.cpp" << 39 << ItemList{
makeItem(5, 7, Usage::Type::Declaration), makeItem(6, 15, Usage::Type::WritableRef),
makeItem(8, 9, Usage::Type::WritableRef), makeItem(9, 11, Usage::Type::WritableRef),
makeItem(11, 4, Usage::Type::Write), makeItem(11, 11, Usage::Type::WritableRef),
makeItem(12, 12, Usage::Type::WritableRef), makeItem(13, 6, Usage::Type::Write),
makeItem(14, 21, Usage::Type::Read), makeItem(15, 4, Usage::Type::Write),
makeItem(15, 12, Usage::Type::Read), makeItem(16, 22, Usage::Type::WritableRef),
makeItem(17, 13, Usage::Type::WritableRef), makeItem(18, 20, Usage::Type::Read),
makeItem(19, 10, Usage::Type::WritableRef), makeItem(20, 10, Usage::Type::Read),
makeItem(21, 11, Usage::Type::WritableRef), makeItem(22, 11, Usage::Type::Read),
makeItem(23, 10, Usage::Type::Read), makeItem(32, 4, Usage::Type::Write),
makeItem(33, 23, Usage::Type::WritableRef), makeItem(34, 23, Usage::Type::Read),
makeItem(35, 15, Usage::Type::WritableRef), makeItem(36, 22, Usage::Type::WritableRef),
makeItem(37, 4, Usage::Type::Read), makeItem(38, 4, Usage::Type::WritableRef),
makeItem(39, 6, Usage::Type::WritableRef), makeItem(40, 4, Usage::Type::Read),
makeItem(41, 4, Usage::Type::WritableRef), makeItem(42, 4, Usage::Type::Read),
makeItem(42, 18, Usage::Type::Read), makeItem(43, 11, Usage::Type::Write),
makeItem(54, 4, Usage::Type::Other), makeItem(55, 4, Usage::Type::Other)};
// Some of 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.
QTest::newRow("struct type") << "defs.h" << 7 << ItemList{
makeItem(1, 7, Usage::Type::Declaration), makeItem(2, 4, Usage::Type::Declaration),
makeItem(20, 19, Usage::Type::Other), makeItem(10, 9, Usage::Type::WritableRef),
makeItem(12, 4, Usage::Type::Write), makeItem(44, 12, Usage::Type::Read),
makeItem(45, 13, Usage::Type::Read), makeItem(47, 12, Usage::Type::Write),
makeItem(50, 8, Usage::Type::Read), makeItem(51, 8, Usage::Type::Write),
makeItem(52, 6, Usage::Type::Write), makeItem(53, 4, Usage::Type::Write),
makeItem(56, 4, Usage::Type::Write), makeItem(56, 22, Usage::Type::Other),
makeItem(58, 10, Usage::Type::Read), makeItem(58, 22, Usage::Type::Read),
makeItem(59, 4, Usage::Type::Write), makeItem(59, 21, Usage::Type::Read)};
QTest::newRow("subclass") << "defs.h" << 450 << ItemList{
makeItem(20, 7, Usage::Type::Declaration), makeItem(5, 4, Usage::Type::Other),
makeItem(13, 21, Usage::Type::Other), makeItem(32, 8, Usage::Type::Other)};
QTest::newRow("array variable") << "main.cpp" << 1134 << ItemList{
makeItem(57, 8, Usage::Type::Declaration), makeItem(58, 4, Usage::Type::Write),
makeItem(59, 15, Usage::Type::Read)};
} }
// The main point here is to test our access type categorization. // 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 // We do not try to stress-test clangd's "Find References" functionality; such tests belong
// into LLVM. // into LLVM.
void ClangdTests::testFindReferences() void ClangdTestFindReferences::test()
{ {
// Find suitable kit. QFETCH(QString, fileName);
const QList<Kit *> allKits = KitManager::kits(); QFETCH(int, pos);
if (allKits.isEmpty()) QFETCH(QList<SearchResultItem>, expectedResults);
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. TextEditor::TextDocument * const doc = document(fileName);
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("find-usages")); QVERIFY(doc);
QVERIFY(testDir.isValid()); QTextCursor cursor(doc->document());
const auto openProjectResult = ProjectExplorerPlugin::openProject( cursor.setPosition(pos);
testDir.absolutePath("find-usages.pro")); client()->findUsages(doc, cursor, {});
QVERIFY2(openProjectResult, qPrintable(openProjectResult.errorMessage())); QVERIFY(waitForSignalOrTimeout(client(), &ClangdClient::findUsagesDone, timeOutInMs()));
openProjectResult.project()->configureAsExampleProject(kit);
// Setting up the project should result in a clangd client being created. QCOMPARE(m_actualResults.size(), expectedResults.size());
// Wait until that has happened. for (int i = 0; i < m_actualResults.size(); ++i) {
const auto modelManagerSupport = ClangModelManagerSupport::instance(); const SearchResultItem &curActual = m_actualResults.at(i);
ClangdClient *client = modelManagerSupport->clientForProject(openProjectResult.project()); const SearchResultItem &curExpected = expectedResults.at(i);
if (!client) { QCOMPARE(curActual.mainRange().begin.line, curExpected.mainRange().begin.line);
QVERIFY(waitForSignalOrTimeout(modelManagerSupport, QCOMPARE(curActual.mainRange().begin.column, curExpected.mainRange().begin.column);
&ClangModelManagerSupport::createdClient, timeOutInMs())); QCOMPARE(curActual.userData(), curExpected.userData());
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, timeOutInMs()));
QVERIFY(client->reachable());
// The kind of AST support we need was introduced in LLVM 13. ClangdTestFollowSymbol::ClangdTestFollowSymbol()
if (client->versionNumber() < QVersionNumber(13)) {
QSKIP("Find Usages test needs clang >= 13"); setProjectFileName("follow-symbol.pro");
setSourceFileNames({"main.cpp", "header.h"});
setMinimumVersion(12);
}
// Wait for index to build. void ClangdTestFollowSymbol::test_data()
if (!client->isFullyIndexed()) {
QVERIFY(waitForSignalOrTimeout(client, &ClangdClient::indexingFinished, timeOutInMs())); QTest::addColumn<QString>("sourceFile");
QVERIFY(client->isFullyIndexed()); QTest::addColumn<int>("sourceLine");
QTest::addColumn<int>("sourceColumn");
QTest::addColumn<QString>("targetFile");
QTest::addColumn<int>("targetLine");
QTest::addColumn<int>("targetColumn");
// Open cpp documents. QTest::newRow("on namespace") << "main.cpp" << 27 << 1 << "header.h" << 28 << 11;
struct EditorCloser { QTest::newRow("class ref") << "main.cpp" << 27 << 9 << "header.h" << 34 << 7;
static void cleanup(IEditor *editor) { EditorManager::closeEditors({editor}); } QTest::newRow("forward decl (same file)") << "header.h" << 32 << 7 << "header.h" << 34 << 7;
QTest::newRow("forward decl (different file") << "header.h" << 48 << 9 << "main.cpp" << 54 << 7;
QTest::newRow("class definition (same file)") << "header.h" << 34 << 7 << "header.h" << 32 << 7;
QTest::newRow("class definition (different file)") << "main.cpp" << 54 << 7
<< "header.h" << 48 << 7;
QTest::newRow("constructor decl") << "header.h" << 36 << 5 << "main.cpp" << 27 << 14;
QTest::newRow("constructor definition") << "main.cpp" << 27 << 14 << "header.h" << 36 << 5;
QTest::newRow("member ref") << "main.cpp" << 39 << 10 << "header.h" << 38 << 18;
QTest::newRow("union member ref") << "main.cpp" << 91 << 20 << "main.cpp" << 86 << 13;
QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18;
QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5;
QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21;
QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5;
QTest::newRow("function def") << "main.cpp" << 35 << 5 << "header.h" << 52 << 5;
QTest::newRow("member function def") << "main.cpp" << 49 << 21 << "header.h" << 43 << 9;
QTest::newRow("member function decl") << "header.h" << 43 << 9 << "main.cpp" << 49 << 21;
QTest::newRow("include") << "main.cpp" << 25 << 13 << "header.h" << 1 << 1;
QTest::newRow("local var") << "main.cpp" << 39 << 6 << "main.cpp" << 36 << 9;
QTest::newRow("alias") << "main.cpp" << 36 << 5 << "main.cpp" << 33 << 7;
QTest::newRow("static var") << "main.cpp" << 40 << 27 << "header.h" << 30 << 7;
QTest::newRow("member function ref (other class)") << "main.cpp" << 62 << 39
<< "cursor.cpp" << 104 << 22;
QTest::newRow("macro ref") << "main.cpp" << 66 << 43 << "header.h" << 27 << 9;
QTest::newRow("on namespace 2") << "main.cpp" << 27 << 3 << "header.h" << 28 << 11;
QTest::newRow("after namespace") << "main.cpp" << 27 << 7 << "header.h" << 28 << 11;
QTest::newRow("operator def") << "main.cpp" << 76 << 13 << "main.cpp" << 72 << 9;
QTest::newRow("operator def 2") << "main.cpp" << 80 << 15 << "main.cpp" << 73 << 10;
QTest::newRow("operator decl") << "main.cpp" << 72 << 12 << "main.cpp" << 76 << 10;
QTest::newRow("operator decl 2") << "main.cpp" << 73 << 12 << "main.cpp" << 80 << 11;
}
void ClangdTestFollowSymbol::test()
{
QFETCH(QString, sourceFile);
QFETCH(int, sourceLine);
QFETCH(int, sourceColumn);
QFETCH(QString, targetFile);
QFETCH(int, targetLine);
QFETCH(int, targetColumn);
TextEditor::TextDocument * const doc = document(sourceFile);
QVERIFY(doc);
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
Utils::Link actualLink;
const auto handler = [&actualLink, &loop](const Utils::Link &l) {
actualLink = l;
loop.quit();
}; };
const auto headerPath = Utils::FilePath::fromString(testDir.absolutePath("defs.h")); QTextCursor cursor(doc->document());
QVERIFY2(headerPath.exists(), qPrintable(headerPath.toUserOutput())); const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn);
QScopedPointer<IEditor, EditorCloser> headerEditor(EditorManager::openEditor(headerPath)); cursor.setPosition(pos);
QVERIFY(headerEditor); client()->followSymbol(doc, cursor, nullptr, handler, true, false);
const auto headerDoc = qobject_cast<TextEditor::TextDocument *>(headerEditor->document()); timer.start(10000);
QVERIFY(headerDoc); loop.exec();
QVERIFY(client->documentForFilePath(headerPath) == headerDoc); QVERIFY(timer.isActive());
const auto cppFilePath = Utils::FilePath::fromString(testDir.absolutePath("main.cpp")); timer.stop();
QVERIFY2(cppFilePath.exists(), qPrintable(cppFilePath.toUserOutput()));
QScopedPointer<IEditor, EditorCloser> cppFileEditor(EditorManager::openEditor(cppFilePath));
QVERIFY(cppFileEditor);
const auto cppDoc = qobject_cast<TextEditor::TextDocument *>(cppFileEditor->document());
QVERIFY(cppDoc);
QVERIFY(client->documentForFilePath(cppFilePath) == cppDoc);
// ... and we're ready to go. QCOMPARE(actualLink.targetFilePath, filePath(targetFile));
QList<SearchResultItem> searchResults; QEXPECT_FAIL("union member ref", "FIXME: clangd points to union", Abort);
connect(client, &ClangdClient::foundReferences, this, QCOMPARE(actualLink.targetLine, targetLine);
[&searchResults](const QList<SearchResultItem> &results) { QCOMPARE(actualLink.targetColumn + 1, targetColumn);
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, timeOutInMs())); \
} 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 Tests

View File

@@ -25,20 +25,83 @@
#pragma once #pragma once
#include <cpptools/cpptoolstestcase.h>
#include <coreplugin/find/searchresultitem.h>
#include <utils/fileutils.h>
#include <QHash>
#include <QObject> #include <QObject>
#include <QStringList>
namespace ProjectExplorer {
class Kit;
class Project;
}
namespace TextEditor { class TextDocument; }
namespace ClangCodeModel { namespace ClangCodeModel {
namespace Internal { namespace Internal {
class ClangdClient;
namespace Tests { namespace Tests {
class ClangdTests : public QObject class ClangdTest : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
~ClangdTest();
protected:
// Convention: base bame == name of parent dir
void setProjectFileName(const QString &fileName) { m_projectFileName = fileName; }
void setSourceFileNames(const QStringList &fileNames) { m_sourceFileNames = fileNames; }
void setMinimumVersion(int version) { m_minVersion = version; }
ClangdClient *client() const { return m_client; }
Utils::FilePath filePath(const QString &fileName) const;
TextEditor::TextDocument *document(const QString &fileName) const {
return m_sourceDocuments.value(fileName);
}
protected slots:
virtual void initTestCase();
private:
CppTools::Tests::TemporaryCopiedDir *m_projectDir = nullptr;
QString m_projectFileName;
QStringList m_sourceFileNames;
QHash<QString, TextEditor::TextDocument *> m_sourceDocuments;
ProjectExplorer::Kit *m_kit = nullptr;
ProjectExplorer::Project *m_project = nullptr;
ClangdClient *m_client = nullptr;
int m_minVersion = -1;
};
class ClangdTestFindReferences : public ClangdTest
{
Q_OBJECT
public:
ClangdTestFindReferences();
private slots: private slots:
void initTestCase(); void initTestCase() override;
void init() { m_actualResults.clear(); }
void test_data();
void test();
void testFindReferences(); private:
QList<Core::SearchResultItem> m_actualResults;
};
class ClangdTestFollowSymbol : public ClangdTest
{
Q_OBJECT
public:
ClangdTestFollowSymbol();
private slots:
void test_data();
void test();
}; };
} // namespace Tests } // namespace Tests

View File

@@ -32,5 +32,10 @@
<file>find-usages/defs.h</file> <file>find-usages/defs.h</file>
<file>find-usages/main.cpp</file> <file>find-usages/main.cpp</file>
<file>find-usages/find-usages.pro</file> <file>find-usages/find-usages.pro</file>
<file>follow-symbol/cursor.cpp</file>
<file>follow-symbol/cursor.h</file>
<file>follow-symbol/follow-symbol.pro</file>
<file>follow-symbol/header.h</file>
<file>follow-symbol/main.cpp</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -0,0 +1,147 @@
#include "cursor.h"
void function(int x)
{
}
namespace Namespace
{
SuperClass::SuperClass(int x) noexcept
: y(x)
{
int LocalVariable;
}
int SuperClass::Method()
{
Method();
AbstractVirtualMethod(y);
int LocalVariable;
return y;
}
int SuperClass::VirtualMethod(int z)
{
AbstractVirtualMethod(z);
return y;
}
bool SuperClass::ConstMethod() const
{
return y;
}
void SuperClass::StaticMethod()
{
using longint = long long int;
using lint = longint;
lint foo;
foo = 30;
const lint bar = 20;
}
}
template <class T>
void TemplateFunction(T LocalVariableParameter)
{
T LocalVariable;
}
Namespace::SuperClass::operator int() const
{
int LocalVariable;
}
int Namespace::SuperClass::operator ++() const
{
int LocalVariable;
return LocalVariable;
}
Namespace::SuperClass::~SuperClass()
{
int LocalVariable;
}
void Struct::FinalVirtualMethod()
{
}
void f1(Struct *FindFunctionCaller)
{
FindFunctionCaller->FinalVirtualMethod();
}
void f2(){
Struct *s = new Struct;
f1(s);
}
void f3()
{
auto FindFunctionCaller = Struct();
FindFunctionCaller.FinalVirtualMethod();
}
void f4()
{
Struct s;
auto *sPointer = &s;
auto sValue = s;
}
void NonFinalStruct::function()
{
FinalVirtualMethod();
}
void OutputFunction(int &out, int in = 1, const int &in2=2, int *out2=nullptr);
void InputFunction(const int &value);
void f5()
{
int OutputValue;
int InputValue = 20;
OutputFunction(OutputValue);
InputFunction(InputValue);
}
void ArgumentCountZero();
void ArgumentCountTwo(int one, const int &two);
void IntegerValue(int);
void LValueReference(int &);
void ConstLValueReference(const int &);
void PointerToConst(const int *);
void Pointer(int *);
void ConstantPointer(int *const);
void ConstIntegerValue(const int);
void NonFinalStruct::ProtectedMethodAccessSpecifier() {}
extern int ExternVarStorageClass;
static void StaticMethodStorageClass() {}
template<class T> const T &InvalidStorageClass(const T &type) { return type; }
namespace Outer {
namespace {
}
enum {
X, Y
};
}

View File

@@ -0,0 +1,44 @@
namespace Namespace
{
class SuperClass;
/**
* A brief comment
*/
class SuperClass
{
SuperClass() = default;
SuperClass(int x) noexcept;
int Method();
virtual int VirtualMethod(int z);
virtual int AbstractVirtualMethod(int z) = 0;
bool ConstMethod() const;
static void StaticMethod();
operator int() const;
int operator ++() const;
~SuperClass();
private:
int y;
};
}
struct Struct final
{
virtual void FinalVirtualMethod() final;
};
union Union
{
};
struct NonFinalStruct
{
virtual void FinalVirtualMethod() final;
void function();
protected:
void ProtectedMethodAccessSpecifier();
};

View File

@@ -0,0 +1,4 @@
TEMPLATE = app
CONFIG -= qt
HEADERS = cursor.h header.h
SOURCES = cursor.cpp main.cpp

View File

@@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2016 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.
**
****************************************************************************/
#pragma once
#define TEST_DEFINE 1
namespace Fooish
{
float flvalue = 100.f;
class Bar;
class Bar {
public:
Bar();
volatile int member = 0;
};
struct Barish
{
int foo(float p, int u);
int mem = 10;
};
}
class FooClass;
int foo(const float p, int u);
int foo();
int foo(float p, int u)
{
return foo() + p + u;
}
int foo(int x, float y);

View File

@@ -0,0 +1,95 @@
/****************************************************************************
**
** Copyright (C) 2016 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 "header.h"
#include "cursor.h"
Fooish::Bar::Bar() {
}
class X;
using YYY = Fooish::Bar;
int foo() {
YYY bar;
bar.member = 30;
Fooish::Barish barish;
bar.member++;
barish.mem = Fooish::flvalue;
barish.foo(1.f, 2);
foo(1, 2.f);
return 1;
X* x;
}
int Fooish::Barish::foo(float p, int u)
{
return ::foo() + p + u;
}
class FooClass
{
public:
FooClass();
static int mememember;
};
FooClass::FooClass() {
NonFinalStruct nfStruct; nfStruct.function();
}
int main() {
return foo() + FooClass::mememember + TEST_DEFINE;
}
class Bar
{
public:
int operator&();
Bar& operator[](int);
};
int Bar::operator&() {
return 0;
}
Bar& Bar::operator[](int) {
return *this;
}
struct S {
union {
int i = 12;
void *something;
};
int func(bool b) {
if (b)
return i;
int i = 42;
return i;
}
};