File indexing completed on 2024-05-12 04:39:15
0001 /* 0002 SPDX-FileCopyrightText: 2012 Olivier de Gaalon <olivier.jg@gmail.com> 0003 SPDX-FileCopyrightText: 2014 David Stevens <dgedstevens@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "test_assistants.h" 0009 0010 #include "codegen/clangrefactoring.h" 0011 #include "codegen/adaptsignatureassistant.h" 0012 0013 #include <QTest> 0014 #include <QLoggingCategory> 0015 #include <QTemporaryDir> 0016 0017 #include <KLocalizedString> 0018 0019 #include <KTextEditor/View> 0020 #include <KTextEditor/Document> 0021 0022 #include <tests/autotestshell.h> 0023 #include <tests/testcore.h> 0024 #include <tests/testfile.h> 0025 0026 #include <util/foregroundlock.h> 0027 0028 #include <interfaces/idocumentcontroller.h> 0029 #include <interfaces/ilanguagecontroller.h> 0030 #include <interfaces/isourceformattercontroller.h> 0031 0032 #include <language/assistant/staticassistant.h> 0033 #include <language/assistant/staticassistantsmanager.h> 0034 #include <language/assistant/renameaction.h> 0035 #include <language/assistant/renameassistant.h> 0036 #include <language/backgroundparser/backgroundparser.h> 0037 #include <language/duchain/duchain.h> 0038 #include <language/duchain/duchainlock.h> 0039 #include <language/duchain/duchainutils.h> 0040 #include <language/codegen/coderepresentation.h> 0041 0042 #include <shell/documentcontroller.h> 0043 0044 #include <clang-c/Index.h> 0045 0046 #include "sanitizer_test_init.h" 0047 0048 using namespace KDevelop; 0049 using namespace KTextEditor; 0050 0051 int main(int argc, char** argv) 0052 { 0053 KDevelop::sanitizerTestInit(argv); 0054 QTEST_MAIN_IMPL(TestAssistants) 0055 } 0056 0057 ForegroundLock *globalTestLock = nullptr; 0058 StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); } 0059 0060 void TestAssistants::initTestCase() 0061 { 0062 QLoggingCategory::setFilterRules(QStringLiteral( 0063 "*.debug=false\n" 0064 "default.debug=true\n" 0065 "kdevelop.plugins.clang.debug=true\n" 0066 )); 0067 QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); 0068 AutoTestShell::init({QStringLiteral("kdevclangsupport"), QStringLiteral("kdevproblemreporter")}); 0069 TestCore::initialize(); 0070 DUChain::self()->disablePersistentStorage(); 0071 Core::self()->languageController()->backgroundParser()->setDelay(0); 0072 Core::self()->sourceFormatterController()->disableSourceFormatting(); 0073 CodeRepresentation::setDiskChangesForbidden(true); 0074 0075 globalTestLock = new ForegroundLock; 0076 } 0077 0078 void TestAssistants::cleanupTestCase() 0079 { 0080 Core::self()->cleanup(); 0081 delete globalTestLock; 0082 globalTestLock = nullptr; 0083 } 0084 0085 static QUrl createFile(const QString& fileContents, const QString& extension, int id) 0086 { 0087 static QTemporaryDir tempDirA; 0088 Q_ASSERT(tempDirA.isValid()); 0089 static QDir dirA(tempDirA.path()); 0090 QFile file(dirA.filePath(QString::number(id) + extension)); 0091 file.open(QIODevice::WriteOnly | QIODevice::Text); 0092 file.write(fileContents.toUtf8()); 0093 file.close(); 0094 return QUrl::fromLocalFile(file.fileName()); 0095 } 0096 0097 class Testbed 0098 { 0099 public: 0100 enum TestDoc 0101 { 0102 HeaderDoc, 0103 CppDoc 0104 }; 0105 0106 enum IncludeBehavior 0107 { 0108 NoAutoInclude, 0109 AutoInclude, 0110 }; 0111 0112 Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude) 0113 : m_includeBehavior(include) 0114 { 0115 static int i = 0; 0116 int id = i; 0117 ++i; 0118 m_headerDocument.url = createFile(headerContents,QStringLiteral(".h"),id); 0119 m_headerDocument.textDoc = openDocument(m_headerDocument.url); 0120 0121 QString preamble; 0122 if (include == AutoInclude) 0123 preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile()); 0124 m_cppDocument.url = createFile(preamble + cppContents,QStringLiteral(".cpp"),id); 0125 m_cppDocument.textDoc = openDocument(m_cppDocument.url); 0126 } 0127 ~Testbed() 0128 { 0129 Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument(); 0130 Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard); 0131 Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard); 0132 } 0133 0134 void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false) 0135 { 0136 TestDocument document; 0137 if (which == CppDoc) 0138 { 0139 document = m_cppDocument; 0140 if (m_includeBehavior == AutoInclude) { 0141 where = Range(where.start().line() + 1, where.start().column(), 0142 where.end().line() + 1, where.end().column()); //The include adds a line 0143 } 0144 } 0145 else { 0146 document = m_headerDocument; 0147 } 0148 // we must activate the document, otherwise we cannot find the correct active view 0149 auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url); 0150 QVERIFY(kdevdoc); 0151 ICore::self()->documentController()->activateDocument(kdevdoc); 0152 auto view = ICore::self()->documentController()->activeTextDocumentView(); 0153 QCOMPARE(view->document(), document.textDoc); 0154 0155 view->setSelection(where); 0156 view->removeSelectionText(); 0157 view->setCursorPosition(where.start()); 0158 view->insertText(what); 0159 QCoreApplication::processEvents(); 0160 if (waitForUpdate) { 0161 DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts); 0162 } 0163 } 0164 0165 QString documentText(TestDoc which) 0166 { 0167 if (which == CppDoc) 0168 { 0169 //The CPP document text shouldn't include the autogenerated include line 0170 QString text = m_cppDocument.textDoc->text(); 0171 return m_includeBehavior == AutoInclude ? text.mid(text.indexOf(QLatin1String("\n")) + 1) : text; 0172 } 0173 else 0174 return m_headerDocument.textDoc->text(); 0175 } 0176 0177 QString includeFileName() const 0178 { 0179 return m_headerDocument.url.toLocalFile(); 0180 } 0181 0182 KTextEditor::Document *document(TestDoc which) const 0183 { 0184 return Core::self()->documentController()->documentForUrl( 0185 which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument(); 0186 } 0187 0188 private: 0189 struct TestDocument { 0190 QUrl url; 0191 Document *textDoc; 0192 }; 0193 0194 Document* openDocument(const QUrl& url) 0195 { 0196 Core::self()->documentController()->openDocument(url); 0197 DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts); 0198 return Core::self()->documentController()->documentForUrl(url)->textDocument(); 0199 } 0200 0201 IncludeBehavior m_includeBehavior; 0202 TestDocument m_headerDocument; 0203 TestDocument m_cppDocument; 0204 }; 0205 0206 0207 /** 0208 * A StateChange describes an insertion/deletion/replacement and the expected result 0209 **/ 0210 struct StateChange 0211 { 0212 StateChange(){}; 0213 StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result) 0214 : document(document) 0215 , range(range) 0216 , newText(newText) 0217 , result(result) 0218 { 0219 } 0220 Testbed::TestDoc document; 0221 Range range; 0222 QString newText; 0223 QString result; 0224 }; 0225 0226 Q_DECLARE_METATYPE(StateChange) 0227 0228 void TestAssistants::testRenameAssistant_data() 0229 { 0230 QTest::addColumn<QString>("fileContents"); 0231 QTest::addColumn<QString>("oldDeclarationName"); 0232 QTest::addColumn<QList<StateChange> >("stateChanges"); 0233 QTest::addColumn<QString>("finalFileContents"); 0234 0235 QTest::newRow("Prepend Text") 0236 << "int foo(int i)\n { i = 0; return i; }" 0237 << "i" 0238 << QList<StateChange>{ 0239 StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"), 0240 StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"), 0241 } 0242 << "int foo(int uzi)\n { uzi = 0; return uzi; }"; 0243 0244 QTest::newRow("Append Text") 0245 << "int foo(int i)\n { i = 0; return i; }" 0246 << "i" 0247 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,13,0,13), QStringLiteral("d"), QStringLiteral("id"))) 0248 << "int foo(int id)\n { id = 0; return id; }"; 0249 0250 QTest::newRow("Replace Text") 0251 << "int foo(int i)\n { i = 0; return i; }" 0252 << "i" 0253 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,12,0,13), QStringLiteral("u"), QStringLiteral("u"))) 0254 << "int foo(int u)\n { u = 0; return u; }"; 0255 0256 QTest::newRow("Paste Replace") 0257 << "int foo(int abg)\n { abg = 0; return abg; }" 0258 << "abg" 0259 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,12,0,15), QStringLiteral("abcdefg"), QStringLiteral("abcdefg"))) 0260 << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; 0261 0262 QTest::newRow("Paste Insert") 0263 << "int foo(int abg)\n { abg = 0; return abg; }" 0264 << "abg" 0265 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,14,0,14), QStringLiteral("cdef"), QStringLiteral("abcdefg"))) 0266 << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; 0267 0268 QTest::newRow("Letter-by-Letter Prepend") 0269 << "int foo(int i)\n { i = 0; return i; }" 0270 << "i" 0271 << (QList<StateChange>() 0272 << StateChange(Testbed::CppDoc, Range(0,12,0,12), QStringLiteral("a"), QStringLiteral("ai")) 0273 << StateChange(Testbed::CppDoc, Range(0,13,0,13), QStringLiteral("b"), QStringLiteral("abi")) 0274 << StateChange(Testbed::CppDoc, Range(0,14,0,14), QStringLiteral("c"), QStringLiteral("abci")) 0275 ) 0276 << "int foo(int abci)\n { abci = 0; return abci; }"; 0277 QTest::newRow("Letter-by-Letter Insert") 0278 << "int foo(int abg)\n { abg = 0; return abg; }" 0279 << "abg" 0280 << (QList<StateChange>() 0281 << StateChange(Testbed::CppDoc, Range(0,14,0,14), QStringLiteral("c"), QStringLiteral("abcg")) 0282 << StateChange(Testbed::CppDoc, Range(0,15,0,15), QStringLiteral("d"), QStringLiteral("abcdg")) 0283 << StateChange(Testbed::CppDoc, Range(0,16,0,16), QStringLiteral("e"), QStringLiteral("abcdeg")) 0284 << StateChange(Testbed::CppDoc, Range(0,17,0,17), QStringLiteral("f"), QStringLiteral("abcdefg")) 0285 ) 0286 << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; 0287 } 0288 0289 ProblemPointer findStaticAssistantProblem(const QVector<ProblemPointer>& problems) 0290 { 0291 const auto renameProblemIt = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) { 0292 return dynamic_cast<const StaticAssistantProblem*>(p.constData()); 0293 }); 0294 if (renameProblemIt != problems.cend()) 0295 return *renameProblemIt; 0296 0297 return {}; 0298 } 0299 0300 template <typename T> 0301 ProblemPointer findProblemWithAssistant(const QVector<ProblemPointer>& problems) 0302 { 0303 const auto problemIterator = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) { 0304 return dynamic_cast<const T*>(p->solutionAssistant().constData()); 0305 }); 0306 if (problemIterator != problems.cend()) 0307 return *problemIterator; 0308 0309 return {}; 0310 } 0311 0312 void TestAssistants::testRenameAssistant() 0313 { 0314 QFETCH(QString, fileContents); 0315 Testbed testbed(QString(), fileContents); 0316 0317 const auto document = testbed.document(Testbed::CppDoc); 0318 QVERIFY(document); 0319 0320 QExplicitlySharedDataPointer<IAssistant> assistant; 0321 0322 QFETCH(QString, oldDeclarationName); 0323 QFETCH(QList<StateChange>, stateChanges); 0324 for (const StateChange& stateChange : qAsConst(stateChanges)) { 0325 testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText, true); 0326 0327 DUChainReadLocker lock; 0328 0329 auto topCtx = DUChain::self()->chainForDocument(document->url()); 0330 QVERIFY(topCtx); 0331 0332 const auto problem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); 0333 if (problem) 0334 assistant = problem->solutionAssistant(); 0335 0336 if (stateChange.result.isEmpty()) { 0337 QVERIFY(!assistant || !assistant->actions().size()); 0338 } else { 0339 qWarning() << assistant.data() << stateChange.result; 0340 QVERIFY(assistant && assistant->actions().size()); 0341 auto *r = qobject_cast<RenameAction*>(assistant->actions().first().data()); 0342 QCOMPARE(r->oldDeclarationName(), oldDeclarationName); 0343 QCOMPARE(r->newDeclarationName(), stateChange.result); 0344 } 0345 } 0346 0347 if (assistant && assistant->actions().size()) { 0348 assistant->actions().first()->execute(); 0349 } 0350 QFETCH(QString, finalFileContents); 0351 QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents); 0352 } 0353 0354 void TestAssistants::testRenameAssistantUndoRename() 0355 { 0356 Testbed testbed(QString(), QStringLiteral("int foo(int i)\n { i = 0; return i; }")); 0357 testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), QStringLiteral("d"), true); 0358 0359 const auto document = testbed.document(Testbed::CppDoc); 0360 QVERIFY(document); 0361 0362 DUChainReadLocker lock; 0363 auto topCtx = DUChain::self()->chainForDocument(document->url()); 0364 QVERIFY(topCtx); 0365 0366 auto firstProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(topCtx)); 0367 QVERIFY(firstProblem); 0368 auto assistant = firstProblem->solutionAssistant(); 0369 QVERIFY(assistant); 0370 0371 QVERIFY(assistant->actions().size() > 0); 0372 auto *r = qobject_cast<RenameAction*>(assistant->actions().first().data()); 0373 qWarning() << topCtx->problems() << assistant->actions().first().data() << assistant->actions().size(); 0374 QVERIFY(r); 0375 0376 // now rename the variable back to its original identifier 0377 testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), QString()); 0378 // there should be no assistant anymore 0379 QVERIFY(!assistant || assistant->actions().isEmpty()); 0380 } 0381 0382 const QString SHOULD_ASSIST = QStringLiteral("SHOULD_ASSIST"); //An assistant will be visible 0383 const QString NO_ASSIST = QStringLiteral("NO_ASSIST"); //No assistant visible 0384 0385 void TestAssistants::testSignatureAssistant_data() 0386 { 0387 QTest::addColumn<QString>("headerContents"); 0388 QTest::addColumn<QString>("cppContents"); 0389 QTest::addColumn<QList<StateChange> >("stateChanges"); 0390 QTest::addColumn<QString>("finalHeaderContents"); 0391 QTest::addColumn<QString>("finalCppContents"); 0392 0393 QTest::newRow("change_argument_type") 0394 << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" 0395 << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" 0396 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), QStringLiteral("char"), SHOULD_ASSIST)) 0397 << "class Foo {\nint bar(char a, char* b, int c = 10); \n};" 0398 << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; 0399 0400 QTest::newRow("prepend_arg_header") 0401 << "class Foo { void bar(int i); };" 0402 << "void Foo::bar(int i)\n{}" 0403 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), QStringLiteral("char c, "), SHOULD_ASSIST)) 0404 << "class Foo { void bar(char c, int i); };" 0405 << "void Foo::bar(char c, int i)\n{}"; 0406 0407 QTest::newRow("prepend_arg_cpp") 0408 << "class Foo { void bar(int i); };" 0409 << "void Foo::bar(int i)\n{}" 0410 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), QStringLiteral("char c, "), SHOULD_ASSIST)) 0411 << "class Foo { void bar(char c, int i); };" 0412 << "void Foo::bar(char c, int i)\n{}"; 0413 0414 QTest::newRow("change_default_parameter") 0415 << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" 0416 << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" 0417 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), QString(), NO_ASSIST)) 0418 << "class Foo {\nint bar(int a, char* b, int c); \n};" 0419 << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; 0420 0421 QTest::newRow("change_function_type") 0422 << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" 0423 << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" 0424 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,0,0,3), QStringLiteral("char"), SHOULD_ASSIST)) 0425 << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};" 0426 << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; 0427 0428 QTest::newRow("swap_args_definition_side") 0429 << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" 0430 << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" 0431 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,13,0,28), QStringLiteral("char* b, int a,"), SHOULD_ASSIST)) 0432 << "class Foo {\nint bar(char* b, int a, int c = 10); \n};" 0433 << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }"; 0434 0435 // see https://bugs.kde.org/show_bug.cgi?id=299393 0436 // actually related to the whitespaces in the header... 0437 QTest::newRow("change_function_constness") 0438 << "class Foo {\nvoid bar(const Foo&) const;\n};" 0439 << "void Foo::bar(const Foo&) const\n{}" 0440 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,25,0,31), QString(), SHOULD_ASSIST)) 0441 << "class Foo {\nvoid bar(const Foo&);\n};" 0442 << "void Foo::bar(const Foo&)\n{}"; 0443 0444 // see https://bugs.kde.org/show_bug.cgi?id=356179 0445 QTest::newRow("keep_static_cpp") 0446 << "class Foo { static void bar(int i); };" 0447 << "void Foo::bar(int i)\n{}" 0448 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), QStringLiteral(", char c"), SHOULD_ASSIST)) 0449 << "class Foo { static void bar(int i, char c); };" 0450 << "void Foo::bar(int i, char c)\n{}"; 0451 QTest::newRow("keep_static_header") 0452 << "class Foo { static void bar(int i); };" 0453 << "void Foo::bar(int i)\n{}" 0454 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), QStringLiteral(", char c"), SHOULD_ASSIST)) 0455 << "class Foo { static void bar(int i, char c); };" 0456 << "void Foo::bar(int i, char c)\n{}"; 0457 0458 // see https://bugs.kde.org/show_bug.cgi?id=356178 0459 QTest::newRow("keep_default_args_cpp_before") 0460 << "class Foo { void bar(bool b, int i = 0); };" 0461 << "void Foo::bar(bool b, int i)\n{}" 0462 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), QStringLiteral("char c, "), SHOULD_ASSIST)) 0463 << "class Foo { void bar(char c, bool b, int i = 0); };" 0464 << "void Foo::bar(char c, bool b, int i)\n{}"; 0465 QTest::newRow("keep_default_args_cpp_after") 0466 << "class Foo { void bar(bool b, int i = 0); };" 0467 << "void Foo::bar(bool b, int i)\n{}" 0468 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), QStringLiteral(", char c"), SHOULD_ASSIST)) 0469 << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };" 0470 << "void Foo::bar(bool b, int i, char c)\n{}"; 0471 QTest::newRow("keep_default_args_header_before") 0472 << "class Foo { void bar(bool b, int i = 0); };" 0473 << "void Foo::bar(bool b, int i)\n{}" 0474 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), QStringLiteral("char c = 'A', "), SHOULD_ASSIST)) 0475 << "class Foo { void bar(bool b, char c = 'A', int i = 0); };" 0476 << "void Foo::bar(bool b, char c, int i)\n{}"; 0477 QTest::newRow("keep_default_args_header_after") 0478 << "class Foo { void bar(bool b, int i = 0); };" 0479 << "void Foo::bar(bool b, int i)\n{}" 0480 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), QStringLiteral(", char c = 'A'"), SHOULD_ASSIST)) 0481 << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };" 0482 << "void Foo::bar(bool b, int i, char c)\n{}"; 0483 0484 // see https://bugs.kde.org/show_bug.cgi?id=355356 0485 QTest::newRow("no_retval_on_ctor") 0486 << "class Foo { Foo(); };" 0487 << "Foo::Foo()\n{}" 0488 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), QStringLiteral("char c"), SHOULD_ASSIST)) 0489 << "class Foo { Foo(char c); };" 0490 << "Foo::Foo(char c)\n{}"; 0491 0492 // see https://bugs.kde.org/show_bug.cgi?id=365420 0493 QTest::newRow("no_retval_on_ctor_while_editing_definition") 0494 << "class Foo {\nFoo(int a); \n};" 0495 << "Foo::Foo(int a)\n{}" 0496 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,13,0,14), QStringLiteral("b"), SHOULD_ASSIST)) 0497 << "class Foo {\nFoo(int b); \n};" 0498 << "Foo::Foo(int b)\n{}"; 0499 0500 // see https://bugs.kde.org/show_bug.cgi?id=298511 0501 QTest::newRow("change_return_type_header") 0502 << "struct Foo { int bar(); };" 0503 << "int Foo::bar()\n{}" 0504 << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 13, 0, 16), QStringLiteral("char"), SHOULD_ASSIST)) 0505 << "struct Foo { char bar(); };" 0506 << "char Foo::bar()\n{}"; 0507 QTest::newRow("change_return_type_impl") 0508 << "struct Foo { int bar(); };" 0509 << "int Foo::bar()\n{}" 0510 << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 0, 0, 3), QStringLiteral("char"), SHOULD_ASSIST)) 0511 << "struct Foo { char bar(); };" 0512 << "char Foo::bar()\n{}"; 0513 } 0514 0515 void TestAssistants::testSignatureAssistant() 0516 { 0517 QFETCH(QString, headerContents); 0518 QFETCH(QString, cppContents); 0519 Testbed testbed(headerContents, cppContents); 0520 0521 QExplicitlySharedDataPointer<IAssistant> assistant; 0522 0523 QFETCH(QList<StateChange>, stateChanges); 0524 for (const StateChange& stateChange : qAsConst(stateChanges)) { 0525 testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true); 0526 0527 const auto document = testbed.document(stateChange.document); 0528 QVERIFY(document); 0529 0530 DUChainReadLocker lock; 0531 0532 auto topCtx = DUChain::self()->chainForDocument(document->url()); 0533 QVERIFY(topCtx); 0534 0535 const auto problem = findProblemWithAssistant<AdaptSignatureAssistant>(DUChainUtils::allProblemsForContext(topCtx)); 0536 if (problem) { 0537 assistant = problem->solutionAssistant(); 0538 } 0539 0540 if (stateChange.result == SHOULD_ASSIST) { 0541 #if CINDEX_VERSION_MINOR < 35 0542 QEXPECT_FAIL("change_function_type", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't parse the code...", Abort); 0543 QEXPECT_FAIL("change_return_type_impl", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't include the function's AST and thus we never get updated about the new return type...", Abort); 0544 #endif 0545 QVERIFY(assistant && !assistant->actions().isEmpty()); 0546 } else { 0547 QVERIFY(!assistant || assistant->actions().isEmpty()); 0548 } 0549 } 0550 0551 if (assistant && !assistant->actions().isEmpty()) 0552 assistant->actions().first()->execute(); 0553 0554 QFETCH(QString, finalHeaderContents); 0555 QFETCH(QString, finalCppContents); 0556 QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents); 0557 QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents); 0558 } 0559 0560 enum UnknownDeclarationAction 0561 { 0562 NoUnknownDeclarationAction = 0x0, 0563 ForwardDecls = 0x1, 0564 MissingInclude = 0x2 0565 }; 0566 Q_DECLARE_FLAGS(UnknownDeclarationActions, UnknownDeclarationAction) 0567 Q_DECLARE_METATYPE(UnknownDeclarationActions) 0568 0569 void TestAssistants::testUnknownDeclarationAssistant_data() 0570 { 0571 QTest::addColumn<QString>("headerContents"); 0572 QTest::addColumn<QString>("globalText"); 0573 QTest::addColumn<QString>("functionText"); 0574 QTest::addColumn<UnknownDeclarationActions>("actions"); 0575 0576 QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test" 0577 << UnknownDeclarationActions(ForwardDecls | MissingInclude); 0578 QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->" 0579 << UnknownDeclarationActions(MissingInclude); 0580 QTest::newRow("unknown_struct") << "" << "" << "test" 0581 << UnknownDeclarationActions(); 0582 QTest::newRow("not a class type") << "void test();" << "" << "test" 0583 << UnknownDeclarationActions(); 0584 } 0585 0586 void TestAssistants::testUnknownDeclarationAssistant() 0587 { 0588 QFETCH(QString, headerContents); 0589 QFETCH(QString, globalText); 0590 QFETCH(QString, functionText); 0591 QFETCH(UnknownDeclarationActions, actions); 0592 0593 static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}"); 0594 Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude); 0595 const auto document = testbed.document(Testbed::CppDoc); 0596 QVERIFY(document); 0597 const int line = document->lines() - 1; 0598 testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true); 0599 0600 DUChainReadLocker lock; 0601 0602 auto topCtx = DUChain::self()->chainForDocument(document->url()); 0603 QVERIFY(topCtx); 0604 0605 const auto problems = topCtx->problems(); 0606 0607 if (actions == NoUnknownDeclarationAction) { 0608 QVERIFY(!problems.isEmpty()); 0609 return; 0610 } 0611 0612 auto firstProblem = problems.first(); 0613 auto assistant = firstProblem->solutionAssistant(); 0614 QVERIFY(assistant); 0615 const auto assistantActions = assistant->actions(); 0616 QStringList actionDescriptions; 0617 for (auto action: assistantActions) { 0618 actionDescriptions << action->description(); 0619 } 0620 0621 { 0622 const bool hasForwardDecls = 0623 actionDescriptions.contains(i18n("Forward declare as 'struct'")) || 0624 actionDescriptions.contains(i18n("Forward declare as 'class'")); 0625 QCOMPARE(hasForwardDecls, static_cast<bool>(actions & ForwardDecls)); 0626 } 0627 0628 { 0629 auto fileName = testbed.includeFileName(); 0630 fileName.remove(0, fileName.lastIndexOf('/') + 1); 0631 const auto directive = QStringLiteral("#include \"%1\"").arg(fileName); 0632 const auto description = i18n("Insert \'%1\'", directive); 0633 const bool hasMissingInclude = actionDescriptions.contains(description); 0634 QCOMPARE(hasMissingInclude, static_cast<bool>(actions & MissingInclude)); 0635 } 0636 } 0637 0638 void TestAssistants::testMoveIntoSource() 0639 { 0640 QFETCH(QString, origHeader); 0641 QFETCH(QString, origImpl); 0642 QFETCH(QString, newHeader); 0643 QFETCH(QString, newImpl); 0644 QFETCH(QualifiedIdentifier, id); 0645 0646 TestFile header(origHeader, QStringLiteral("h")); 0647 TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, QStringLiteral("cpp"), &header); 0648 0649 { 0650 TopDUContext* headerCtx = nullptr; 0651 { 0652 DUChainReadLocker lock; 0653 headerCtx = DUChain::self()->chainForDocument(header.url()); 0654 } 0655 // Here is a problem: when launching tests one by one, we can reuse the same tmp file for headers. 0656 // But because of document chain for header wasn't unloaded properly in previous run we reuse it here 0657 // Therefore when using headerCtx->findDeclarations below we find declarations from the previous launch -> tests fail 0658 if (headerCtx) { 0659 // TODO: Investigate why this chain doesn't get updated when parsing source file 0660 DUChainWriteLocker lock; 0661 DUChain::self()->removeDocumentChain(headerCtx); 0662 } 0663 } 0664 0665 impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); 0666 QVERIFY(impl.waitForParsed()); 0667 0668 IndexedDeclaration declaration; 0669 { 0670 DUChainReadLocker lock; 0671 auto headerCtx = DUChain::self()->chainForDocument(header.url()); 0672 QVERIFY(headerCtx); 0673 auto decls = headerCtx->findDeclarations(id); 0674 Q_ASSERT(!decls.isEmpty()); 0675 declaration = IndexedDeclaration(decls.first()); 0676 QVERIFY(declaration.isValid()); 0677 } 0678 CodeRepresentation::setDiskChangesForbidden(false); 0679 ClangRefactoring refactoring; 0680 QCOMPARE(refactoring.moveIntoSource(declaration), QString()); 0681 CodeRepresentation::setDiskChangesForbidden(true); 0682 0683 QCOMPARE(header.fileContents(), newHeader); 0684 QVERIFY(impl.fileContents().endsWith(newImpl)); 0685 } 0686 0687 void TestAssistants::testMoveIntoSource_data() 0688 { 0689 QTest::addColumn<QString>("origHeader"); 0690 QTest::addColumn<QString>("origImpl"); 0691 QTest::addColumn<QString>("newHeader"); 0692 QTest::addColumn<QString>("newImpl"); 0693 QTest::addColumn<QualifiedIdentifier>("id"); 0694 0695 const QualifiedIdentifier fooId(QStringLiteral("foo")); 0696 0697 QTest::newRow("globalfunction") << QStringLiteral("int foo()\n{\n int i = 0;\n return 0;\n}\n") 0698 << QString() 0699 << QStringLiteral("int foo();\n") 0700 << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") 0701 << fooId; 0702 0703 QTest::newRow("staticfunction") << QStringLiteral("static int foo()\n{\n int i = 0;\n return 0;\n}\n") 0704 << QString() 0705 << QStringLiteral("static int foo();\n") 0706 << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") 0707 << fooId; 0708 0709 QTest::newRow("funcsameline") << QStringLiteral("int foo() {\n int i = 0;\n return 0;\n}\n") 0710 << QString() 0711 << QStringLiteral("int foo();\n") 0712 << QStringLiteral("\nint foo() {\n int i = 0;\n return 0;\n}\n") 0713 << fooId; 0714 0715 QTest::newRow("func-comment") << QStringLiteral("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") 0716 << QString() 0717 << QStringLiteral("int foo()\n/* foobar */;\n") 0718 << QStringLiteral("\nint foo() {\n int i = 0;\n return 0;\n}\n") 0719 << fooId; 0720 0721 QTest::newRow("func-comment2") << QStringLiteral("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") 0722 << QString() 0723 << QStringLiteral("int foo()\n/*asdf*/;\n") 0724 << QStringLiteral("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") 0725 << fooId; 0726 0727 const QualifiedIdentifier aFooId(QStringLiteral("a::foo")); 0728 QTest::newRow("class-method") << QStringLiteral("class a {\n int foo(){\n return 0;\n }\n};\n") 0729 << QString() 0730 << QStringLiteral("class a {\n int foo();\n};\n") 0731 << QStringLiteral("\nint a::foo() {\n return 0;\n }\n") 0732 << aFooId; 0733 0734 QTest::newRow("class-method-const") << QStringLiteral("class a {\n int foo() const\n {\n return 0;\n }\n};\n") 0735 << QString() 0736 << QStringLiteral("class a {\n int foo() const;\n};\n") 0737 << QStringLiteral("\nint a::foo() const\n {\n return 0;\n }\n") 0738 << aFooId; 0739 0740 QTest::newRow("class-method-const-sameline") << QStringLiteral("class a {\n int foo() const{\n return 0;\n }\n};\n") 0741 << QString() 0742 << QStringLiteral("class a {\n int foo() const;\n};\n") 0743 << QStringLiteral("\nint a::foo() const {\n return 0;\n }\n") 0744 << aFooId; 0745 QTest::newRow("elaborated-type") << QStringLiteral("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") 0746 << QString() 0747 << QStringLiteral("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") 0748 << QStringLiteral("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") 0749 << aFooId; 0750 QTest::newRow("add-into-namespace") << QStringLiteral("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") 0751 << QStringLiteral("namespace NS{\n}") 0752 << QStringLiteral("namespace NS{class a {\nint foo() const;\n};\n}") 0753 << QStringLiteral("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") 0754 << QualifiedIdentifier(QStringLiteral("NS::a::foo")); 0755 QTest::newRow("class-template-parameter") 0756 << QStringLiteral(R"( 0757 namespace first { 0758 template <typename T> 0759 class Test{}; 0760 0761 namespace second { 0762 template <typename T> 0763 class List; 0764 } 0765 0766 class MoveIntoSource 0767 { 0768 public: 0769 void f(const second::List<const volatile Test<first::second::List<int*>>*>& param){} 0770 };} 0771 )") 0772 << QString() 0773 << QStringLiteral(R"( 0774 namespace first { 0775 template <typename T> 0776 class Test{}; 0777 0778 namespace second { 0779 template <typename T> 0780 class List; 0781 } 0782 0783 class MoveIntoSource 0784 { 0785 public: 0786 void f(const second::List<const volatile Test<first::second::List<int*>>*>& param); 0787 };} 0788 )") 0789 << QStringLiteral("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") 0790 << QualifiedIdentifier(QStringLiteral("first::MoveIntoSource::f")); 0791 0792 QTest::newRow("move-unexposed-type") 0793 << QStringLiteral("namespace std { template<typename _CharT> class basic_string; \ntypedef basic_string<char> string;}\n void move(std::string i){}") 0794 << QString() 0795 << QStringLiteral("namespace std { template<typename _CharT> class basic_string; \ntypedef basic_string<char> string;}\n void move(std::string i);") 0796 << QStringLiteral("void move(std::string i) {}\n") 0797 << QualifiedIdentifier(QStringLiteral("move")); 0798 QTest::newRow("move-constructor") 0799 << QStringLiteral("class Class{Class(){}\n};") 0800 << QString() 0801 << QStringLiteral("class Class{Class();\n};") 0802 << QStringLiteral("Class::Class() {}\n") 0803 << QualifiedIdentifier(QStringLiteral("Class::Class")); 0804 } 0805 0806 void TestAssistants::testHeaderGuardAssistant() 0807 { 0808 CodeRepresentation::setDiskChangesForbidden(false); 0809 0810 QFETCH(QString, filename); 0811 QFETCH(QString, code); 0812 QFETCH(QString, pragmaExpected); 0813 QFETCH(QString, macroExpected); 0814 0815 TestFile pragmaFile(code, QStringLiteral("h")); 0816 TestFile macroFile(code, QStringLiteral("h"), filename); 0817 TestFile impl("#include \"" + pragmaFile.url().str() + "\"\n" 0818 "#include \"" + macroFile.url().str() + "\"\n", QStringLiteral("cpp")); 0819 0820 QExplicitlySharedDataPointer<IAssistant> pragmaAssistant; 0821 QExplicitlySharedDataPointer<IAssistant> macroAssistant; 0822 0823 QVERIFY(impl.parseAndWait(TopDUContext::Empty)); 0824 0825 DUChainReadLocker lock; 0826 QVERIFY(impl.topContext()); 0827 0828 const auto pragmaTopContext = DUChain::self()->chainForDocument(pragmaFile.url()); 0829 const auto macroTopContext = DUChain::self()->chainForDocument(macroFile.url()); 0830 QVERIFY(pragmaTopContext); 0831 QVERIFY(macroTopContext); 0832 0833 const auto pragmaProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(pragmaTopContext)); 0834 const auto macroProblem = findStaticAssistantProblem(DUChainUtils::allProblemsForContext(macroTopContext)); 0835 QVERIFY(pragmaProblem && macroProblem); 0836 pragmaAssistant = pragmaProblem->solutionAssistant(); 0837 macroAssistant = macroProblem->solutionAssistant(); 0838 QVERIFY(pragmaAssistant && macroAssistant); 0839 0840 pragmaAssistant->actions()[0]->execute(); 0841 macroAssistant->actions()[1]->execute(); 0842 0843 QCOMPARE(pragmaFile.fileContents(), pragmaExpected); 0844 QCOMPARE(macroFile.fileContents(), macroExpected); 0845 0846 CodeRepresentation::setDiskChangesForbidden(true); 0847 } 0848 0849 void TestAssistants::testHeaderGuardAssistant_data() 0850 { 0851 QTest::addColumn<QString>("filename"); 0852 QTest::addColumn<QString>("code"); 0853 QTest::addColumn<QString>("pragmaExpected"); 0854 QTest::addColumn<QString>("macroExpected"); 0855 0856 QTest::newRow("simple") << QStringLiteral("simpleheaderguard") 0857 << QStringLiteral("int main()\n{\nreturn 0;\n}\n") 0858 << QStringLiteral("#pragma once\n\nint main()\n{\nreturn 0;\n}\n") 0859 << QStringLiteral( 0860 "#ifndef SIMPLEHEADERGUARD_H_INCLUDED\n" 0861 "#define SIMPLEHEADERGUARD_H_INCLUDED\n\n" 0862 "int main()\n{\nreturn 0;\n}\n\n" 0863 "#endif // SIMPLEHEADERGUARD_H_INCLUDED" 0864 ); 0865 0866 QTest::newRow("licensed") << QStringLiteral("licensed-headerguard") 0867 << QStringLiteral("/* Copyright 3019 John Doe\n */\n// Some comment\n" 0868 "int main()\n{\nreturn 0;\n}\n") 0869 << QStringLiteral("/* Copyright 3019 John Doe\n */\n// Some comment\n" 0870 "#pragma once\n\n" 0871 "int main()\n{\nreturn 0;\n}\n") 0872 << QStringLiteral( 0873 "/* Copyright 3019 John Doe\n */\n// Some comment\n" 0874 "#ifndef LICENSED_HEADERGUARD_H_INCLUDED\n" 0875 "#define LICENSED_HEADERGUARD_H_INCLUDED\n\n" 0876 "int main()\n{\nreturn 0;\n}\n\n" 0877 "#endif // LICENSED_HEADERGUARD_H_INCLUDED" 0878 ); 0879 0880 QTest::newRow("empty") << QStringLiteral("empty-file") 0881 << QStringLiteral("") 0882 << QStringLiteral("#pragma once\n\n") 0883 << QStringLiteral("#ifndef EMPTY_FILE_H_INCLUDED\n" 0884 "#define EMPTY_FILE_H_INCLUDED\n\n\n" 0885 "#endif // EMPTY_FILE_H_INCLUDED" 0886 ); 0887 0888 QTest::newRow("no-trailinig-newline") << QStringLiteral("no-endline-file") 0889 << QStringLiteral("int foo;") 0890 << QStringLiteral("#pragma once\n\nint foo;") 0891 << QStringLiteral("#ifndef NO_ENDLINE_FILE_H_INCLUDED\n" 0892 "#define NO_ENDLINE_FILE_H_INCLUDED\n\n" 0893 "int foo;\n" 0894 "#endif // NO_ENDLINE_FILE_H_INCLUDED" 0895 ); 0896 0897 QTest::newRow("whitespace-at-start") << QStringLiteral("whitespace-at-start") 0898 << QStringLiteral("\nint foo;") 0899 << QStringLiteral("#pragma once\n\n\nint foo;") 0900 << QStringLiteral( 0901 "#ifndef WHITESPACE_AT_START_H_INCLUDED\n" 0902 "#define WHITESPACE_AT_START_H_INCLUDED\n\n" 0903 "\nint foo;\n" 0904 "#endif // WHITESPACE_AT_START_H_INCLUDED" 0905 ); 0906 } 0907 0908 #include "moc_test_assistants.cpp"