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"