test_assistants.cpp 32.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* This file is part of KDevelop
     Copyright 2012 Olivier de Gaalon <olivier.jg@gmail.com>
               2014 David Stevens <dgedstevens@gmail.com>

     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Library General Public
     License version 2 as published by the Free Software Foundation.

     This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
     Library General Public License for more details.

     You should have received a copy of the GNU Library General Public License
     along with this library; see the file COPYING.LIB. If not, write to
     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

#include "test_assistants.h"

Sergey Kalinichev's avatar
Sergey Kalinichev committed
22
#include "codegen/clangrefactoring.h"
23

24
#include <QtTest/QtTest>
Olivier de Gaalon's avatar
Olivier de Gaalon committed
25
#include <QTemporaryDir>
26

Milian Wolff's avatar
Milian Wolff committed
27
28
29
#include <ktexteditor/view.h>
#include <ktexteditor/document.h>

30
31
#include <tests/autotestshell.h>
#include <tests/testcore.h>
32
#include <tests/testfile.h>
Milian Wolff's avatar
Milian Wolff committed
33

Kevin Funk's avatar
Kevin Funk committed
34
#include <util/foregroundlock.h>
Milian Wolff's avatar
Milian Wolff committed
35

36
37
38
39
#include <interfaces/idocumentcontroller.h>
#include <interfaces/ilanguagecontroller.h>
#include <interfaces/iplugincontroller.h>
#include <interfaces/isourceformattercontroller.h>
Milian Wolff's avatar
Milian Wolff committed
40

41
42
43
#include <language/assistant/staticassistant.h>
#include <language/assistant/staticassistantsmanager.h>
#include <language/assistant/renameaction.h>
44
#include <language/assistant/renameassistant.h>
45
46
#include <language/backgroundparser/backgroundparser.h>
#include <language/duchain/duchain.h>
47
#include <language/duchain/duchainlock.h>
48
49
#include <language/duchain/duchainutils.h>
#include <language/codegen/coderepresentation.h>
Milian Wolff's avatar
Milian Wolff committed
50

51
52
53
54
55
#include <shell/documentcontroller.h>

using namespace KDevelop;
using namespace KTextEditor;

Kevin Funk's avatar
Kevin Funk committed
56
QTEST_MAIN(TestAssistants)
57
58
59
60
61
62

ForegroundLock *globalTestLock = 0;
StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); }

void TestAssistants::initTestCase()
{
Kevin Funk's avatar
Kevin Funk committed
63
64
65
66
67
    QLoggingCategory::setFilterRules(QStringLiteral(
        "*.debug=false\n"
        "default.debug=true\n"
        "kdevelop.plugins.clang.debug=true\n"
    ));
68
    QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport"));
69
    QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1"));
70
    AutoTestShell::init({QStringLiteral("kdevclangsupport"), QStringLiteral("kdevproblemreporter")});
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    TestCore::initialize();
    DUChain::self()->disablePersistentStorage();
    Core::self()->languageController()->backgroundParser()->setDelay(0);
    Core::self()->sourceFormatterController()->disableSourceFormatting(true);
    CodeRepresentation::setDiskChangesForbidden(true);

    globalTestLock = new ForegroundLock;
}

void TestAssistants::cleanupTestCase()
{
    Core::self()->cleanup();
    delete globalTestLock;
    globalTestLock = 0;
}

Olivier de Gaalon's avatar
Olivier de Gaalon committed
87
static QUrl createFile(const QString& fileContents, QString extension, int id)
88
{
Olivier de Gaalon's avatar
Olivier de Gaalon committed
89
90
91
92
    static QTemporaryDir tempDirA;
    Q_ASSERT(tempDirA.isValid());
    static QDir dirA(tempDirA.path());
    QFile file(dirA.filePath(QString::number(id) + extension));
93
94
95
    file.open(QIODevice::WriteOnly | QIODevice::Text);
    file.write(fileContents.toUtf8());
    file.close();
Olivier de Gaalon's avatar
Olivier de Gaalon committed
96
    return QUrl::fromLocalFile(file.fileName());
97
98
99
100
101
102
103
104
105
106
107
}

class Testbed
{
public:
    enum TestDoc
    {
        HeaderDoc,
        CppDoc
    };

108
109
110
111
112
113
114
115
    enum IncludeBehavior
    {
        NoAutoInclude,
        AutoInclude,
    };

    Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude)
        : m_includeBehavior(include)
116
117
118
119
120
121
122
    {
        static int i = 0;
        int id = i;
        ++i;
        m_headerDocument.url = createFile(headerContents,".h",id);
        m_headerDocument.textDoc = openDocument(m_headerDocument.url);

123
124
125
126
        QString preamble;
        if (include == AutoInclude)
            preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile());
        m_cppDocument.url = createFile(preamble + cppContents,".cpp",id);
127
128
129
130
        m_cppDocument.textDoc = openDocument(m_cppDocument.url);
    }
    ~Testbed()
    {
131
        Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument();
132
133
134
135
136
137
138
139
140
141
        Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard);
        Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard);
    }

    void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false)
    {
        TestDocument document;
        if (which == CppDoc)
        {
            document = m_cppDocument;
142
143
144
145
            if (m_includeBehavior == AutoInclude) {
                where = Range(where.start().line() + 1, where.start().column(),
                              where.end().line() + 1, where.end().column()); //The include adds a line
            }
146
147
148
149
        }
        else {
            document = m_headerDocument;
        }
150
151
152
153
154
155
156
        // we must activate the document, otherwise we cannot find the correct active view
        auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url);
        QVERIFY(kdevdoc);
        ICore::self()->documentController()->activateDocument(kdevdoc);
        auto view = ICore::self()->documentController()->activeTextDocumentView();
        QCOMPARE(view->document(), document.textDoc);

Kevin Funk's avatar
Kevin Funk committed
157
158
159
160
        view->setSelection(where);
        view->removeSelectionText();
        view->setCursorPosition(where.start());
        view->insertText(what);
161
162
163
164
165
166
167
168
169
170
171
172
        QCoreApplication::processEvents();
        if (waitForUpdate) {
            DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts);
        }
    }

    QString documentText(TestDoc which)
    {
        if (which == CppDoc)
        {
            //The CPP document text shouldn't include the autogenerated include line
            QString text = m_cppDocument.textDoc->text();
173
            return m_includeBehavior == AutoInclude ? text.mid(text.indexOf("\n") + 1) : text;
174
175
176
177
        }
        else
            return m_headerDocument.textDoc->text();
    }
178
179
180
181
182
183
184
185
186
187
188
189

    QString includeFileName() const
    {
        return m_headerDocument.url.toLocalFile();
    }

    KTextEditor::Document *document(TestDoc which) const
    {
        return Core::self()->documentController()->documentForUrl(
            which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument();
    }

190
191
private:
    struct TestDocument {
Olivier de Gaalon's avatar
Olivier de Gaalon committed
192
        QUrl url;
193
194
195
        Document *textDoc;
    };

Olivier de Gaalon's avatar
Olivier de Gaalon committed
196
    Document* openDocument(const QUrl& url)
197
198
199
200
201
202
    {
        Core::self()->documentController()->openDocument(url);
        DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts);
        return Core::self()->documentController()->documentForUrl(url)->textDocument();
    }

203
    IncludeBehavior m_includeBehavior;
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    TestDocument m_headerDocument;
    TestDocument m_cppDocument;
};


/**
 * A StateChange describes an insertion/deletion/replacement and the expected result
**/
struct StateChange
{
    StateChange(){};
    StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result)
        : document(document)
        , range(range)
        , newText(newText)
        , result(result)
    {
    }
    Testbed::TestDoc document;
    Range range;
    QString newText;
    QString result;
};

Q_DECLARE_METATYPE(StateChange)
Q_DECLARE_METATYPE(QList<StateChange>)

231
232
233
234
235
236
237
238
239
240
void TestAssistants::testRenameAssistant_data()
{
    QTest::addColumn<QString>("fileContents");
    QTest::addColumn<QString>("oldDeclarationName");
    QTest::addColumn<QList<StateChange> >("stateChanges");
    QTest::addColumn<QString>("finalFileContents");

    QTest::newRow("Prepend Text")
        << "int foo(int i)\n { i = 0; return i; }"
        << "i"
241
242
243
244
245
        << QList<StateChange>{
            StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"),
            StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"),
        }
        << "int foo(int uzi)\n { uzi = 0; return uzi; }";
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

    QTest::newRow("Append Text")
        << "int foo(int i)\n { i = 0; return i; }"
        << "i"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,13,0,13), "d", "id"))
        << "int foo(int id)\n { id = 0; return id; }";

    QTest::newRow("Replace Text")
        << "int foo(int i)\n { i = 0; return i; }"
        << "i"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "u", "u"))
        << "int foo(int u)\n { u = 0; return u; }";

    QTest::newRow("Paste Replace")
        << "int foo(int abg)\n { abg = 0; return abg; }"
        << "abg"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,12,0,15), "abcdefg", "abcdefg"))
        << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }";

    QTest::newRow("Paste Insert")
        << "int foo(int abg)\n { abg = 0; return abg; }"
        << "abg"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "cdef", "abcdefg"))
        << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }";

271
272
273
274
275
276
277
278
    QTest::newRow("Letter-by-Letter Prepend")
        << "int foo(int i)\n { i = 0; return i; }"
        << "i"
        << (QList<StateChange>()
            << StateChange(Testbed::CppDoc, Range(0,12,0,12), "a", "ai")
            << StateChange(Testbed::CppDoc, Range(0,13,0,13), "b", "abi")
            << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abci")
        )
Kevin Funk's avatar
Kevin Funk committed
279
        << "int foo(int abci)\n { abci = 0; return abci; }";
280
281
282
283
284
285
286
287
288
289
290
291
    QTest::newRow("Letter-by-Letter Insert")
        << "int foo(int abg)\n { abg = 0; return abg; }"
        << "abg"
        << (QList<StateChange>()
            << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abcg")
            << StateChange(Testbed::CppDoc, Range(0,15,0,15), "d", "abcdg")
            << StateChange(Testbed::CppDoc, Range(0,16,0,16), "e", "abcdeg")
            << StateChange(Testbed::CppDoc, Range(0,17,0,17), "f", "abcdefg")
        )
        << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }";
}

292
293
294
295
296
297
298
299
300
301
302
ProblemPointer findStaticAssistantProblem(const QList<ProblemPointer>& problems)
{
    const auto renameProblemIt = std::find_if(problems.cbegin(), problems.cend(), [](const ProblemPointer& p) {
        return dynamic_cast<const StaticAssistantProblem*>(p.constData());
    });
    if (renameProblemIt != problems.cend())
        return *renameProblemIt;

    return {};
}

303
304
305
306
307
void TestAssistants::testRenameAssistant()
{
    QFETCH(QString, fileContents);
    Testbed testbed("", fileContents);

308
309
310
311
312
    const auto document = testbed.document(Testbed::CppDoc);
    QVERIFY(document);

    QExplicitlySharedDataPointer<IAssistant> assistant;

313
314
315
316
    QFETCH(QString, oldDeclarationName);
    QFETCH(QList<StateChange>, stateChanges);
    foreach(StateChange stateChange, stateChanges)
    {
317
318
319
320
321
322
323
324
325
326
327
        testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText, true);

        DUChainReadLocker lock;

        auto topCtx = DUChain::self()->chainForDocument(document->url());
        QVERIFY(topCtx);

        const auto problem = findStaticAssistantProblem(topCtx->problems());
        if (problem)
            assistant = problem->solutionAssistant();

328
        if (stateChange.result.isEmpty()) {
329
            QVERIFY(!assistant || !assistant->actions().size());
330
        } else {
331
332
333
            qWarning() << assistant.data() << stateChange.result;
            QVERIFY(assistant && assistant->actions().size());
            RenameAction *r = qobject_cast<RenameAction*>(assistant->actions().first().data());
334
335
336
337
            QCOMPARE(r->oldDeclarationName(), oldDeclarationName);
            QCOMPARE(r->newDeclarationName(), stateChange.result);
        }
    }
338
339
340

    if (assistant && assistant->actions().size()) {
        assistant->actions().first()->execute();
341
342
343
344
345
346
347
348
    }
    QFETCH(QString, finalFileContents);
    QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents);
}

void TestAssistants::testRenameAssistantUndoRename()
{
    Testbed testbed("", "int foo(int i)\n { i = 0; return i; }");
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
    testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), "d", true);

    const auto document = testbed.document(Testbed::CppDoc);
    QVERIFY(document);

    DUChainReadLocker lock;
    auto topCtx = DUChain::self()->chainForDocument(document->url());
    QVERIFY(topCtx);

    auto firstProblem = findStaticAssistantProblem(topCtx->problems());
    auto assistant = firstProblem->solutionAssistant();
    QVERIFY(assistant);

    QVERIFY(assistant->actions().size() > 0);
    RenameAction *r = qobject_cast<RenameAction*>(assistant->actions().first().data());
    qWarning() << topCtx->problems() << assistant->actions().first().data() << assistant->actions().size();
365
366
367
368
369
    QVERIFY(r);

    // now rename the variable back to its original identifier
    testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), "");
    // there should be no assistant anymore
370
    QVERIFY(!assistant || assistant->actions().isEmpty());
371
372
}

373
374
375
376
377
378
379
380
381
382
383
const QString SHOULD_ASSIST = "SHOULD_ASSIST"; //An assistant will be visible
const QString NO_ASSIST = "NO_ASSIST";               //No assistant visible

void TestAssistants::testSignatureAssistant_data()
{
    QTest::addColumn<QString>("headerContents");
    QTest::addColumn<QString>("cppContents");
    QTest::addColumn<QList<StateChange> >("stateChanges");
    QTest::addColumn<QString>("finalHeaderContents");
    QTest::addColumn<QString>("finalCppContents");

384
    QTest::newRow("change_argument_type")
385
386
387
388
389
390
      << "class Foo {\nint bar(int a, char* b, int c = 10); \n};"
      << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"
      << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), "char", SHOULD_ASSIST))
      << "class Foo {\nint bar(char a, char* b, int c = 10); \n};"
      << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }";

391
392
393
394
395
396
397
398
399
400
401
402
403
404
    QTest::newRow("prepend_arg_header")
      << "class Foo { void bar(int i); };"
      << "void Foo::bar(int i)\n{}"
      << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), "char c, ", SHOULD_ASSIST))
      << "class Foo { void bar(char c, int i); };"
      << "void Foo::bar(char c, int i)\n{}";

    QTest::newRow("prepend_arg_cpp")
      << "class Foo { void bar(int i); };"
      << "void Foo::bar(int i)\n{}"
      << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST))
      << "class Foo { void bar(char c, int i); };"
      << "void Foo::bar(char c, int i)\n{}";

405
    QTest::newRow("change_default_parameter")
406
407
408
409
410
411
        << "class Foo {\nint bar(int a, char* b, int c = 10); \n};"
        << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), "", NO_ASSIST))
        << "class Foo {\nint bar(int a, char* b, int c); \n};"
        << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }";

412
    QTest::newRow("change_function_type")
413
414
415
416
417
418
        << "class Foo {\nint bar(int a, char* b, int c = 10); \n};"
        << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,0,0,3), "char", SHOULD_ASSIST))
        << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};"
        << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }";

419
    QTest::newRow("swap_args_definition_side")
420
421
422
423
424
425
426
427
        << "class Foo {\nint bar(int a, char* b, int c = 10); \n};"
        << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,13,0,28), "char* b, int a,", SHOULD_ASSIST))
        << "class Foo {\nint bar(char* b, int a, int c = 10); \n};"
        << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }";

    // see https://bugs.kde.org/show_bug.cgi?id=299393
    // actually related to the whitespaces in the header...
428
    QTest::newRow("change_function_constness")
429
430
431
432
433
        << "class Foo {\nvoid bar(const Foo&) const;\n};"
        << "void Foo::bar(const Foo&) const\n{}"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0,25,0,31), "", SHOULD_ASSIST))
        << "class Foo {\nvoid bar(const Foo&);\n};"
        << "void Foo::bar(const Foo&)\n{}";
434
435
436
437
438
439
440
441
442
443
444
445
446
447

    // see https://bugs.kde.org/show_bug.cgi?id=356179
    QTest::newRow("keep_static_cpp")
        << "class Foo { static void bar(int i); };"
        << "void Foo::bar(int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), ", char c", SHOULD_ASSIST))
        << "class Foo { static void bar(int i, char c); };"
        << "void Foo::bar(int i, char c)\n{}";
    QTest::newRow("keep_static_header")
        << "class Foo { static void bar(int i); };"
        << "void Foo::bar(int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), ", char c", SHOULD_ASSIST))
        << "class Foo { static void bar(int i, char c); };"
        << "void Foo::bar(int i, char c)\n{}";
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473

    // see https://bugs.kde.org/show_bug.cgi?id=356178
    QTest::newRow("keep_default_args_cpp_before")
        << "class Foo { void bar(bool b, int i = 0); };"
        << "void Foo::bar(bool b, int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST))
        << "class Foo { void bar(char c, bool b, int i = 0); };"
        << "void Foo::bar(char c, bool b, int i)\n{}";
    QTest::newRow("keep_default_args_cpp_after")
        << "class Foo { void bar(bool b, int i = 0); };"
        << "void Foo::bar(bool b, int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), ", char c", SHOULD_ASSIST))
        << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };"
        << "void Foo::bar(bool b, int i, char c)\n{}";
    QTest::newRow("keep_default_args_header_before")
        << "class Foo { void bar(bool b, int i = 0); };"
        << "void Foo::bar(bool b, int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), "char c = 'A', ", SHOULD_ASSIST))
        << "class Foo { void bar(bool b, char c = 'A', int i = 0); };"
        << "void Foo::bar(bool b, char c, int i)\n{}";
    QTest::newRow("keep_default_args_header_after")
        << "class Foo { void bar(bool b, int i = 0); };"
        << "void Foo::bar(bool b, int i)\n{}"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), ", char c = 'A'", SHOULD_ASSIST))
        << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };"
        << "void Foo::bar(bool b, int i, char c)\n{}";
474
475
476
477
478
479
480
481

    // see https://bugs.kde.org/show_bug.cgi?id=355356
    QTest::newRow("no_retval_on_ctor")
        << "class Foo { Foo(); };"
        << "Foo::Foo()\n{}"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), "char c", SHOULD_ASSIST))
        << "class Foo { Foo(char c); };"
        << "Foo::Foo(char c)\n{}";
482
483
484
485
486
487
488
489
490
491
492
493
494
495

    // see https://bugs.kde.org/show_bug.cgi?id=298511
    QTest::newRow("change_return_type_header")
        << "struct Foo { int bar(); };"
        << "int Foo::bar()\n{}"
        << (QList<StateChange>() << StateChange(Testbed::HeaderDoc, Range(0, 13, 0, 16), "char", SHOULD_ASSIST))
        << "struct Foo { char bar(); };"
        << "char Foo::bar()\n{}";
    QTest::newRow("change_return_type_impl")
        << "struct Foo { int bar(); };"
        << "int Foo::bar()\n{}"
        << (QList<StateChange>() << StateChange(Testbed::CppDoc, Range(0, 0, 0, 3), "char", SHOULD_ASSIST))
        << "struct Foo { char bar(); };"
        << "char Foo::bar()\n{}";
496
497
498
499
500
501
502
503
}

void TestAssistants::testSignatureAssistant()
{
    QFETCH(QString, headerContents);
    QFETCH(QString, cppContents);
    Testbed testbed(headerContents, cppContents);

504
505
    QExplicitlySharedDataPointer<IAssistant> assistant;

506
507
508
509
510
    QFETCH(QList<StateChange>, stateChanges);
    foreach (StateChange stateChange, stateChanges)
    {
        testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true);

511
512
513
514
515
516
517
518
519
520
521
522
523
        const auto document = testbed.document(stateChange.document);
        QVERIFY(document);

        DUChainReadLocker lock;

        auto topCtx = DUChain::self()->chainForDocument(document->url());
        QVERIFY(topCtx);

        const auto problem = findStaticAssistantProblem(topCtx->problems());
        if (problem) {
            assistant = problem->solutionAssistant();
        }

524
        if (stateChange.result == SHOULD_ASSIST) {
Sergey Kalinichev's avatar
Sergey Kalinichev committed
525
            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);
526
            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);
527
            QVERIFY(assistant && !assistant->actions().isEmpty());
528
        } else {
529
            QVERIFY(!assistant || assistant->actions().isEmpty());
530
531
        }
    }
532
533
534
535

    DUChainReadLocker lock;
    if (assistant && !assistant->actions().isEmpty())
        assistant->actions().first()->execute();
536
537
538
539
540
541

    QFETCH(QString, finalHeaderContents);
    QFETCH(QString, finalCppContents);
    QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents);
    QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents);
}
542

543
enum UnknownDeclarationAction
544
{
545
    NoUnknownDeclarationAction = 0x0,
546
547
548
    ForwardDecls = 0x1,
    MissingInclude = 0x2
};
549
Q_DECLARE_FLAGS(UnknownDeclarationActions, UnknownDeclarationAction)
550
551
552
553
554
555
556
557
558
559
Q_DECLARE_METATYPE(UnknownDeclarationActions)

void TestAssistants::testUnknownDeclarationAssistant_data()
{
    QTest::addColumn<QString>("headerContents");
    QTest::addColumn<QString>("globalText");
    QTest::addColumn<QString>("functionText");
    QTest::addColumn<UnknownDeclarationActions>("actions");

    QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test"
560
        << UnknownDeclarationActions(ForwardDecls | MissingInclude);
561
    QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->"
562
        << UnknownDeclarationActions(MissingInclude);
563
    QTest::newRow("unknown_struct") << "" << "" << "test"
564
        << UnknownDeclarationActions();
565
566
567
568
569
570
571
572
573
574
575
}

void TestAssistants::testUnknownDeclarationAssistant()
{
    QFETCH(QString, headerContents);
    QFETCH(QString, globalText);
    QFETCH(QString, functionText);
    QFETCH(UnknownDeclarationActions, actions);

    static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}");
    Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude);
576
577
578
    const auto document = testbed.document(Testbed::CppDoc);
    QVERIFY(document);
    const int line = document->lines() - 1;
579
580
    testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true);

581
582
583
584
585
586
587
    DUChainReadLocker lock;

    auto topCtx = DUChain::self()->chainForDocument(document->url());
    QVERIFY(topCtx);

    const auto problems = topCtx->problems();

588
    if (actions == NoUnknownDeclarationAction) {
589
        QVERIFY(!problems.isEmpty());
590
591
592
        return;
    }

593
594
595
596
    auto firstProblem = problems.first();
    auto assistant = firstProblem->solutionAssistant();
    QVERIFY(assistant);
    const auto assistantActions = assistant->actions();
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
    QStringList actionDescriptions;
    for (auto action: assistantActions) {
        actionDescriptions << action->description();
    }

    {
        const bool hasForwardDecls =
            actionDescriptions.contains(QObject::tr("Forward declare as 'struct'")) &&
            actionDescriptions.contains(QObject::tr("Forward declare as 'class'"));
        QCOMPARE(hasForwardDecls, static_cast<bool>(actions & ForwardDecls));
    }

    {
        auto fileName = testbed.includeFileName();
        fileName = fileName.mid(fileName.lastIndexOf('/') + 1);
        const auto description = QObject::tr("Insert \'%1\'")
            .arg(QStringLiteral("#include \"%1\"").arg(fileName));
        const bool hasMissingInclude = actionDescriptions.contains(description);
        QCOMPARE(hasMissingInclude, static_cast<bool>(actions & MissingInclude));
    }
}
618
619
620
621
622
623
624
625
626
627
628
629

void TestAssistants::testMoveIntoSource()
{
    QFETCH(QString, origHeader);
    QFETCH(QString, origImpl);
    QFETCH(QString, newHeader);
    QFETCH(QString, newImpl);
    QFETCH(QualifiedIdentifier, id);

    TestFile header(origHeader, "h");
    TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header);

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
    {
        TopDUContext* headerCtx = nullptr;
        {
            DUChainReadLocker lock;
            headerCtx = DUChain::self()->chainForDocument(header.url());
        }
        // Here is a problem: when launching tests one by one, we can reuse the same tmp file for headers.
        // But because of document chain for header wasn't unloaded properly in previous run we reuse it here
        // Therefore when using headerCtx->findDeclarations below we find declarations from the previous launch -> tests fail
        if (headerCtx) {
            // TODO: Investigate why this chain doesn't get updated when parsing source file
            DUChainWriteLocker lock;
            DUChain::self()->removeDocumentChain(headerCtx);
        }
    }

646
647
648
649
650
651
652
653
654
655
656
657
658
659
    impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses);
    QVERIFY(impl.waitForParsed());

    IndexedDeclaration declaration;
    {
        DUChainReadLocker lock;
        auto headerCtx = DUChain::self()->chainForDocument(header.url());
        QVERIFY(headerCtx);
        auto decls = headerCtx->findDeclarations(id);
        Q_ASSERT(!decls.isEmpty());
        declaration = IndexedDeclaration(decls.first());
        QVERIFY(declaration.isValid());
    }
    CodeRepresentation::setDiskChangesForbidden(false);
Sergey Kalinichev's avatar
Sergey Kalinichev committed
660
    ClangRefactoring refactoring;
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
    QCOMPARE(refactoring.moveIntoSource(declaration), QString());
    CodeRepresentation::setDiskChangesForbidden(true);

    QCOMPARE(header.fileContents(), newHeader);
    QVERIFY(impl.fileContents().endsWith(newImpl));
}

void TestAssistants::testMoveIntoSource_data()
{
    QTest::addColumn<QString>("origHeader");
    QTest::addColumn<QString>("origImpl");
    QTest::addColumn<QString>("newHeader");
    QTest::addColumn<QString>("newImpl");
    QTest::addColumn<QualifiedIdentifier>("id");

    const QualifiedIdentifier fooId("foo");

    QTest::newRow("globalfunction") << QString("int foo()\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << QString()
                                    << QString("int foo();\n")
                                    << QString("\nint foo()\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << fooId;

    QTest::newRow("staticfunction") << QString("static int foo()\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << QString()
                                    << QString("static int foo();\n")
                                    << QString("\nint foo()\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << fooId;

    QTest::newRow("funcsameline") << QString("int foo() {\n    int i = 0;\n    return 0;\n}\n")
                                    << QString()
                                    << QString("int foo();\n")
                                    << QString("\nint foo() {\n    int i = 0;\n    return 0;\n}\n")
                                    << fooId;

    QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n    int i = 0;\n    return 0;\n}\n")
                                    << QString()
                                    << QString("int foo()\n/* foobar */;\n")
                                    << QString("\nint foo() {\n    int i = 0;\n    return 0;\n}\n")
                                    << fooId;

    QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << QString()
                                    << QString("int foo()\n/*asdf*/;\n")
                                    << QString("\nint foo()\n{\n    int i = 0;\n    return 0;\n}\n")
                                    << fooId;

    const QualifiedIdentifier aFooId("a::foo");
    QTest::newRow("class-method") << QString("class a {\n    int foo(){\n        return 0;\n    }\n};\n")
                                    << QString()
                                    << QString("class a {\n    int foo();\n};\n")
                                    << QString("\nint a::foo() {\n        return 0;\n    }\n")
                                    << aFooId;

    QTest::newRow("class-method-const") << QString("class a {\n    int foo() const\n    {\n        return 0;\n    }\n};\n")
                                    << QString()
                                    << QString("class a {\n    int foo() const;\n};\n")
                                    << QString("\nint a::foo() const\n    {\n        return 0;\n    }\n")
                                    << aFooId;

    QTest::newRow("class-method-const-sameline") << QString("class a {\n    int foo() const{\n        return 0;\n    }\n};\n")
                                    << QString()
                                    << QString("class a {\n    int foo() const;\n};\n")
                                    << QString("\nint a::foo() const {\n        return 0;\n    }\n")
                                    << aFooId;
726
727
728
729
730
    QTest::newRow("elaborated-type") << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n")
                                    << QString()
                                    << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n")
                                    << QString("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n")
                                    << aFooId;
731
732
733
734
735
    QTest::newRow("add-into-namespace") << QString("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}")
                                    << QString("namespace NS{\n}")
                                    << QString("namespace NS{class a {\nint foo() const;\n};\n}")
                                    << QString("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}")
                                    << QualifiedIdentifier("NS::a::foo");
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
    QTest::newRow("class-template-parameter")
        << QString(R"(
            namespace first {
            template <typename T>
            class Test{};

            namespace second {
                template <typename T>
                class List;
            }

            class MoveIntoSource
            {
            public:
                void f(const second::List<const volatile Test<first::second::List<int*>>*>& param){}
            };}
        )")
        << QString("")
        << QString(R"(
            namespace first {
            template <typename T>
            class Test{};

            namespace second {
                template <typename T>
                class List;
            }

            class MoveIntoSource
            {
            public:
                void f(const second::List<const volatile Test<first::second::List<int*>>*>& param);
            };}
        )")
        << QString("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n")
        << QualifiedIdentifier("first::MoveIntoSource::f");
772
773
774
775
776
777
778

        QTest::newRow("move-unexposed-type")
            << QString("namespace std { template<typename _CharT> class basic_string; \ntypedef basic_string<char> string;}\n void move(std::string i){}")
            << QString("")
            << QString("namespace std { template<typename _CharT> class basic_string; \ntypedef basic_string<char> string;}\n void move(std::string i);")
            << QString("void move(std::string i) {}\n")
            << QualifiedIdentifier("move");
779
780
781
782
783
784
        QTest::newRow("move-constructor")
            << QString("class Class{Class(){}\n};")
            << QString("")
            << QString("class Class{Class();\n};")
            << QString("Class::Class() {}\n")
            << QualifiedIdentifier("Class::Class");
785
}