testtreemodel.cpp 37 KB
Newer Older
Christian Stenger's avatar
Christian Stenger committed
1
2
/****************************************************************************
**
Christian Stenger's avatar
Christian Stenger committed
3
4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
Christian Stenger's avatar
Christian Stenger committed
5
**
Christian Stenger's avatar
Christian Stenger committed
6
** This file is part of Qt Creator.
Christian Stenger's avatar
Christian Stenger committed
7
**
Christian Stenger's avatar
Christian Stenger committed
8
9
10
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
Christian Stenger's avatar
Christian Stenger committed
11
** Software or, alternatively, in accordance with the terms contained in
Christian Stenger's avatar
Christian Stenger committed
12
13
14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
Christian Stenger's avatar
Christian Stenger committed
15
**
Christian Stenger's avatar
Christian Stenger committed
16
17
18
19
20
21
22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
Christian Stenger's avatar
Christian Stenger committed
23
24
25
**
****************************************************************************/

26
#include "autotestconstants.h"
Christian Stenger's avatar
Christian Stenger committed
27
28
29
30
#include "testcodeparser.h"
#include "testtreeitem.h"
#include "testtreemodel.h"

31
#include <cpptools/cppmodelmanager.h>
Christian Stenger's avatar
Christian Stenger committed
32

33
34
35
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>

36
37
#include <qmljs/qmljsmodelmanagerinterface.h>

38
39
#include <texteditor/texteditor.h>

40
#include <utils/qtcassert.h>
41

Christian Stenger's avatar
Christian Stenger committed
42
43
44
namespace Autotest {
namespace Internal {

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class ReferencingFilesFinder : public Utils::TreeItemVisitor
{
public:
    ReferencingFilesFinder() {}

    bool preVisit(Utils::TreeItem *item) override
    {
        // 0 = invisible root, 1 = main categories, 2 = test cases, 3 = test functions
        return item->level() < 4;
    }

    void visit(Utils::TreeItem *item) override
    {
        // skip invisible root item
        if (!item->parent())
            return;

        if (auto testItem = static_cast<TestTreeItem *>(item)) {
            if (!testItem->filePath().isEmpty() && !testItem->referencingFile().isEmpty())
                m_referencingFiles.insert(testItem->filePath(), testItem->referencingFile());
        }
    }

    QMap<QString, QString> referencingFiles() const { return m_referencingFiles; }

private:
    QMap<QString, QString> m_referencingFiles;

};

/***********************************************************************************************/

Christian Stenger's avatar
Christian Stenger committed
77
TestTreeModel::TestTreeModel(QObject *parent) :
78
    TreeModel(parent),
Christian Stenger's avatar
Christian Stenger committed
79
80
    m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::Root)),
    m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::Root)),
81
    m_googleTestRootItem(new TestTreeItem(tr("Google Tests"), QString(), TestTreeItem::Root)),
82
    m_parser(new TestCodeParser(this)),
83
    m_connectionsInitialized(false)
Christian Stenger's avatar
Christian Stenger committed
84
{
85
86
    rootItem()->appendChild(m_autoTestRootItem);
    rootItem()->appendChild(m_quickTestRootItem);
87
    rootItem()->appendChild(m_googleTestRootItem);
88

89
90
91
92
93
94
95
96
    connect(m_parser, &TestCodeParser::cacheCleared, this,
            &TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
    connect(m_parser, &TestCodeParser::testItemCreated,
            this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection);
    connect(m_parser, &TestCodeParser::testItemModified,
            this, &TestTreeModel::modifyTestTreeItem, Qt::QueuedConnection);
    connect(m_parser, &TestCodeParser::testItemsRemoved,
            this, &TestTreeModel::removeTestTreeItems, Qt::QueuedConnection);
97
    connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated,
98
            this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection);
99
    connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved,
100
            this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection);
101
102
    connect(m_parser, &TestCodeParser::gTestsRemoved,
            this, &TestTreeModel::removeGTests, Qt::QueuedConnection);
103

Christian Stenger's avatar
Christian Stenger committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//    CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance();
//    if (cppMM) {
//        // replace later on by
//        // cppMM->registerAstProcessor([this](const CplusPlus::Document::Ptr &doc,
//        //                             const CPlusPlus::Snapshot &snapshot) {
//        //        checkForQtTestStuff(doc, snapshot);
//        //      });
//        connect(cppMM, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
//                this, SLOT(checkForQtTestStuff(CPlusPlus::Document::Ptr)),
//                Qt::DirectConnection);

//    }
}

static TestTreeModel *m_instance = 0;

TestTreeModel *TestTreeModel::instance()
{
    if (!m_instance)
        m_instance = new TestTreeModel;
    return m_instance;
}

TestTreeModel::~TestTreeModel()
{
    m_instance = 0;
}

132
133
void TestTreeModel::enableParsing()
{
134
    m_refCounter.ref();
135
136
137
138

    if (!m_connectionsInitialized)
        m_parser->setDirty();

139
    m_parser->setState(TestCodeParser::Idle);
140
141
142
143
144
    if (m_connectionsInitialized)
        return;

    ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance();
    connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged,
145
            m_parser, &TestCodeParser::onStartupProjectChanged);
146
147
148
149
150
151

    CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
    connect(cppMM, &CppTools::CppModelManager::documentUpdated,
            m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
    connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles,
            m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection);
152
153
    connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated,
            m_parser, &TestCodeParser::onProjectPartsUpdated);
154
155
156
157
158
159
160
161
162
163
164

    QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance();
    connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
            m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
    connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
            m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection);
    m_connectionsInitialized = true;
}

void TestTreeModel::disableParsing()
{
165
166
    if (!m_refCounter.deref())
        m_parser->setState(TestCodeParser::Disabled);
167
168
}

Christian Stenger's avatar
Christian Stenger committed
169
170
171
172
173
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

174
175
176
177
178
    TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
    if (item && item->setData(index.column(), value, role)) {
        emit dataChanged(index, index);
        if (role == Qt::CheckStateRole) {
            switch (item->type()) {
Christian Stenger's avatar
Christian Stenger committed
179
            case TestTreeItem::TestClass:
180
            case TestTreeItem::GTestCase:
181
            case TestTreeItem::GTestCaseParameterized:
182
                if (item->childCount() > 0)
Christian Stenger's avatar
Christian Stenger committed
183
184
                    emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
                break;
Christian Stenger's avatar
Christian Stenger committed
185
            case TestTreeItem::TestFunction:
186
            case TestTreeItem::GTestName:
Christian Stenger's avatar
Christian Stenger committed
187
188
189
190
191
192
                emit dataChanged(index.parent(), index.parent());
                break;
            default: // avoid warning regarding unhandled enum member
                break;
            }
        }
193
        return true;
Christian Stenger's avatar
Christian Stenger committed
194
195
196
197
198
199
200
201
202
    }
    return false;
}

Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable;

203
    TestTreeItem *item = static_cast<TestTreeItem *>(itemForIndex(index));
Christian Stenger's avatar
Christian Stenger committed
204
    switch(item->type()) {
Christian Stenger's avatar
Christian Stenger committed
205
    case TestTreeItem::TestClass:
206
    case TestTreeItem::GTestCase:
207
    case TestTreeItem::GTestCaseParameterized:
208
        if (item->name().isEmpty())
209
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
210
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
211
    case TestTreeItem::TestFunction:
212
    case TestTreeItem::GTestName:
213
        if (item->parentItem()->name().isEmpty())
214
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
215
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
216
    case TestTreeItem::Root:
Christian Stenger's avatar
Christian Stenger committed
217
        return Qt::ItemIsEnabled;
Christian Stenger's avatar
Christian Stenger committed
218
219
220
    case TestTreeItem::TestDataFunction:
    case TestTreeItem::TestSpecialFunction:
    case TestTreeItem::TestDataTag:
221
222
    default:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
223
224
225
226
227
    }
}

bool TestTreeModel::hasTests() const
{
228
229
    return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0
            || m_googleTestRootItem->childCount() > 0;
Christian Stenger's avatar
Christian Stenger committed
230
231
}

232
233
234
235
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
    QList<TestConfiguration *> result;

236
237
238
239
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

240
241
    // get all Auto Tests
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
242
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
243

244
245
        TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(),
                                                      child->childCount());
246
247
        tc->setMainFilePath(child->filePath());
        tc->setProject(project);
248
249
        result << tc;
    }
250
251
252
253

    // get all Quick Tests
    QMap<QString, int> foundMains;
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
254
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
255
256
257
        // unnamed Quick Tests must be handled separately
        if (child->name().isEmpty()) {
            for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) {
258
                const TestTreeItem *grandChild = child->childItem(childRow);
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
                const QString mainFile = grandChild->mainFile();
                foundMains.insert(mainFile, foundMains.contains(mainFile)
                                            ? foundMains.value(mainFile) + 1 : 1);
            }
            continue;
        }
        // named Quick Test
        const QString mainFile = child->mainFile();
        foundMains.insert(mainFile, foundMains.contains(mainFile)
                          ? foundMains.value(mainFile) + child->childCount()
                          : child->childCount());
    }
    // create TestConfiguration for each main
    foreach (const QString &mainFile, foundMains.keys()) {
        TestConfiguration *tc = new TestConfiguration(QString(), QStringList(),
                                                      foundMains.value(mainFile));
275
276
        tc->setMainFilePath(mainFile);
        tc->setProject(project);
277
278
279
        result << tc;
    }

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    foundMains.clear();

    // get all Google Tests
    for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) {
        const TestTreeItem *child = m_googleTestRootItem->childItem(row);
        for (int childRow = 0, childCount = child->childCount(); childRow < childCount; ++childRow) {
            const QString &proFilePath = child->childItem(childRow)->mainFile();
            foundMains.insert(proFilePath, foundMains.contains(proFilePath)
                              ? foundMains.value(proFilePath) + 1 : 1);
        }
    }

    foreach (const QString &proFile, foundMains.keys()) {
        TestConfiguration *tc = new TestConfiguration(QString(), QStringList(),
                                                      foundMains.value(proFile));
        tc->setProFile(proFile);
        tc->setProject(project);
297
        tc->setTestType(TestTypeGTest);
298
299
300
        result << tc;
    }

301
302
303
304
305
306
    return result;
}

QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
    QList<TestConfiguration *> result;
307
308
309
310
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

311
    TestConfiguration *testConfiguration = 0;
312

313
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
314
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
315
316
317
318
319

        switch (child->checked()) {
        case Qt::Unchecked:
            continue;
        case Qt::Checked:
320
            testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount());
321
322
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
323
            result << testConfiguration;
324
325
326
            continue;
        case Qt::PartiallyChecked:
        default:
327
            const QString childName = child->name();
328
329
330
            int grandChildCount = child->childCount();
            QStringList testCases;
            for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
331
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
332
333
334
335
                if (grandChild->checked() == Qt::Checked)
                    testCases << grandChild->name();
            }

336
            testConfiguration = new TestConfiguration(childName, testCases);
337
338
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
339
            result << testConfiguration;
340
341
        }
    }
342
343
344
345
346
347
348
    // Quick Tests must be handled differently - need the calling cpp file to use this in
    // addProjectInformation() - additionally this must be unique to not execute the same executable
    // on and on and on...
    // TODO: do this later on for Auto Tests as well to support strange setups? or redo the model

    QMap<QString, TestConfiguration *> foundMains;

349
350
    if (TestTreeItem *unnamed = unnamedQuickTests()) {
        for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) {
351
            const TestTreeItem *grandChild = unnamed->childItem(childRow);
352
353
            const QString mainFile = grandChild->mainFile();
            if (foundMains.contains(mainFile)) {
354
355
356
                QTC_ASSERT(testConfiguration,
                           qWarning() << "Illegal state (unnamed Quick Test listed as named)";
                           return QList<TestConfiguration *>());
357
                foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1);
358
            } else {
359
360
361
                testConfiguration = new TestConfiguration(QString(), QStringList());
                testConfiguration->setTestCaseCount(1);
                testConfiguration->setUnnamedOnly(true);
362
363
                testConfiguration->setMainFilePath(mainFile);
                testConfiguration->setProject(project);
364
                foundMains.insert(mainFile, testConfiguration);
365
            }
366
367
368
369
        }
    }

    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
370
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
371
372
373
374
375
376
377
378
379
380
381
382
383
384
        // unnamed Quick Tests have been handled separately already
        if (child->name().isEmpty())
            continue;

        // named Quick Tests
        switch (child->checked()) {
        case Qt::Unchecked:
            continue;
        case Qt::Checked:
        case Qt::PartiallyChecked:
        default:
            QStringList testFunctions;
            int grandChildCount = child->childCount();
            for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
385
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
Christian Stenger's avatar
Christian Stenger committed
386
                if (grandChild->type() != TestTreeItem::TestFunction)
387
                    continue;
388
                testFunctions << child->name() + QLatin1String("::") + grandChild->name();
389
390
391
392
393
394
395
396
397
            }
            TestConfiguration *tc;
            if (foundMains.contains(child->mainFile())) {
                tc = foundMains[child->mainFile()];
                QStringList oldFunctions(tc->testCases());
                // if oldFunctions.size() is 0 this test configuration is used for at least one
                // unnamed test case
                if (oldFunctions.size() == 0) {
                    tc->setTestCaseCount(tc->testCaseCount() + testFunctions.size());
398
                    tc->setUnnamedOnly(false);
399
400
401
402
403
404
                } else {
                    oldFunctions << testFunctions;
                    tc->setTestCases(oldFunctions);
                }
            } else {
                tc = new TestConfiguration(QString(), testFunctions);
405
406
                tc->setMainFilePath(child->mainFile());
                tc->setProject(project);
407
408
409
410
411
412
413
                foundMains.insert(child->mainFile(), tc);
            }
            break;
        }
    }

    foreach (TestConfiguration *config, foundMains.values())
414
415
        if (!config->unnamedOnly())
            result << config;
416

417
418
419
420
421
422
423
424
425
426
427
428
429
    // get selected Google Tests
    QMap<QString, QStringList> proFilesWithEnabledTestSets;

    for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) {
        const TestTreeItem *child = m_googleTestRootItem->childItem(row);
        if (child->checked() == Qt::Unchecked) // add this test name to disabled list ?
            continue;

        int grandChildCount = child->childCount();
        for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
            const TestTreeItem *grandChild = child->childItem(grandChildRow);
            const QString &proFile = grandChild->mainFile();
            QStringList enabled = proFilesWithEnabledTestSets.value(proFile);
430
431
432
433
434
435
436
437
            if (grandChild->checked() == Qt::Checked) {
                QString testSpecifier = child->name() + QLatin1Char('.') + grandChild->name();
                if (child->type() == TestTreeItem::GTestCaseParameterized) {
                    testSpecifier.prepend(QLatin1String("*/"));
                    testSpecifier.append(QLatin1String("/*"));
                }
                enabled << testSpecifier;
            }
438
439
440
441
442
443
444
            proFilesWithEnabledTestSets.insert(proFile, enabled);
        }
    }

    foreach (const QString &proFile, proFilesWithEnabledTestSets.keys()) {
        TestConfiguration *tc = new TestConfiguration(QString(),
                                                      proFilesWithEnabledTestSets.value(proFile));
445
        tc->setTestType(TestTypeGTest);
446
447
448
449
450
        tc->setProFile(proFile);
        tc->setProject(project);
        result << tc;
    }

451
452
453
    return result;
}

454
455
456
457
458
459
460
461
TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) const
{
    QTC_ASSERT(item != 0, return 0);
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    QTC_ASSERT(project, return 0);

    TestConfiguration *config = 0;
    switch (item->type()) {
Christian Stenger's avatar
Christian Stenger committed
462
    case TestTreeItem::TestClass: {
463
464
465
466
        if (item->parent() == m_quickTestRootItem) {
            // Quick Test TestCase
            QStringList testFunctions;
            for (int row = 0, count = item->childCount(); row < count; ++row) {
467
468
                    testFunctions << item->name() + QLatin1String("::")
                                     + item->childItem(row)->name();
469
470
471
472
473
474
475
476
477
478
479
480
            }
            config = new TestConfiguration(QString(), testFunctions);
            config->setMainFilePath(item->mainFile());
            config->setProject(project);
        } else {
            // normal auto test
            config = new TestConfiguration(item->name(), QStringList(), item->childCount());
            config->setMainFilePath(item->filePath());
            config->setProject(project);
        }
        break;
    }
Christian Stenger's avatar
Christian Stenger committed
481
    case TestTreeItem::TestFunction: {
482
        const TestTreeItem *parent = item->parentItem();
483
484
485
486
487
488
489
490
491
492
493
494
495
496
        if (parent->parent() == m_quickTestRootItem) {
            // it's a Quick Test function of a named TestCase
            QStringList testFunction(parent->name() + QLatin1String("::") + item->name());
            config = new TestConfiguration(QString(), testFunction);
            config->setMainFilePath(parent->mainFile());
            config->setProject(project);
        } else {
            // normal auto test
            config = new TestConfiguration(parent->name(), QStringList() << item->name());
            config->setMainFilePath(parent->filePath());
            config->setProject(project);
        }
        break;
    }
Christian Stenger's avatar
Christian Stenger committed
497
    case TestTreeItem::TestDataTag: {
498
499
        const TestTreeItem *function = item->parentItem();
        const TestTreeItem *parent = function ? function->parentItem() : 0;
500
501
502
503
504
505
506
507
        if (!parent)
            return 0;
        const QString functionWithTag = function->name() + QLatin1Char(':') + item->name();
        config = new TestConfiguration(parent->name(), QStringList() << functionWithTag);
        config->setMainFilePath(parent->filePath());
        config->setProject(project);
        break;
    }
508
509
510
511
512
513
    case TestTreeItem::GTestCase:
    case TestTreeItem::GTestCaseParameterized: {
        QString testSpecifier = item->name() + QLatin1String(".*");
        if (item->type() == TestTreeItem::GTestCaseParameterized)
            testSpecifier.prepend(QLatin1String("*/"));

514
        if (int childCount = item->childCount()) {
515
            config = new TestConfiguration(QString(), QStringList(testSpecifier));
516
517
518
            config->setTestCaseCount(childCount);
            config->setProFile(item->childItem(0)->mainFile());
            config->setProject(project);
519
            config->setTestType(TestTypeGTest);
520
521
522
523
524
        }
        break;
    }
    case TestTreeItem::GTestName: {
        const TestTreeItem *parent = item->parentItem();
525
526
527
528
529
530
531
        QString testSpecifier = parent->name() + QLatin1Char('.') + item->name();

        if (parent->type() == TestTreeItem::GTestCaseParameterized) {
            testSpecifier.prepend(QLatin1String("*/"));
            testSpecifier.append(QLatin1String("/*"));
        }
        config = new TestConfiguration(QString(), QStringList(testSpecifier));
532
533
        config->setProFile(item->mainFile());
        config->setProject(project);
534
        config->setTestType(TestTypeGTest);
535
536
        break;
    }
537
538
539
540
541
542
543
    // not supported items
    default:
        return 0;
    }
    return config;
}

544
545
546
QString TestTreeModel::getMainFileForUnnamedQuickTest(const QString &qmlFile) const
{
    const TestTreeItem *unnamed = unnamedQuickTests();
Christian Stenger's avatar
Christian Stenger committed
547
548
    const int count = unnamed ? unnamed->childCount() : 0;
    for (int row = 0; row < count; ++row) {
549
        const TestTreeItem *child = unnamed->childItem(row);
550
551
552
553
554
555
        if (qmlFile == child->filePath())
            return child->mainFile();
    }
    return QString();
}

556
void TestTreeModel::qmlFilesForMainFile(const QString &mainFile, QSet<QString> *filePaths) const
557
{
558
    const TestTreeItem *unnamed = unnamedQuickTests();
559
560
    if (!unnamed)
        return;
561
562
    for (int row = 0, count = unnamed->childCount(); row < count; ++row) {
        const TestTreeItem *child = unnamed->childItem(row);
563
        if (child->mainFile() == mainFile)
564
565
566
567
568
569
            filePaths->insert(child->filePath());
    }
}

QList<QString> TestTreeModel::getUnnamedQuickTestFunctions() const
{
570
    const TestTreeItem *unnamed = unnamedQuickTests();
571
572
573
574
575
576
577
578
    if (unnamed)
        return unnamed->getChildNames();
    return QList<QString>();
}

bool TestTreeModel::hasUnnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row)
579
        if (m_quickTestRootItem->childItem(row)->name().isEmpty())
580
581
582
583
            return true;
    return false;
}

584
585
586
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
587
        TestTreeItem *child = m_quickTestRootItem->childItem(row);
588
589
590
591
592
593
        if (child->name().isEmpty())
            return child;
    }
    return 0;
}

594
void TestTreeModel::removeUnnamedQuickTests(const QString &filePath)
Christian Stenger's avatar
Christian Stenger committed
595
{
596
597
598
    TestTreeItem *unnamedQT = unnamedQuickTests();
    if (!unnamedQT)
        return;
599

600
    for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) {
601
        TestTreeItem *child = unnamedQT->childItem(childRow);
602
        if (filePath == child->filePath())
603
            delete takeItem(child);
604
    }
605
606

    if (unnamedQT->childCount() == 0)
607
        delete takeItem(unnamedQT);
608
609
610
    emit testTreeModelChanged();
}

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
void TestTreeModel::removeGTests(const QString &filePath)
{
    for (int childRow = m_googleTestRootItem->childCount() - 1; childRow >= 0; --childRow) {
        TestTreeItem *child = m_googleTestRootItem->childItem(childRow);
        for (int grandChildRow = child->childCount() - 1; grandChildRow >= 0; --grandChildRow) {
            TestTreeItem *grandChild = child->childItem(grandChildRow);
            if (filePath == grandChild->filePath())
                delete takeItem(grandChild);
        }
        if (child->childCount() == 0)
            delete takeItem(child);
    }
    emit testTreeModelChanged();
}

626
627
628
629
630
631
632
QMap<QString, QString> TestTreeModel::referencingFiles() const
{
    ReferencingFilesFinder finder;
    rootItem()->walkTree(&finder);
    return finder.referencingFiles();
}

633
void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type)
634
{
635
    TestTreeItem *parent = rootItemForType(type);
636
637
638
639
640
    if (type == TestTreeModel::GoogleTest) {
        // check if there's already an item with the same test name...
        TestTreeItem *toBeUpdated = 0;
        for (int row = 0, count = parent->childCount(); row < count; ++row) {
            TestTreeItem *current = parent->childItem(row);
641
            if (current->name() == item->name() && current->type() == item->type()) {
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
                toBeUpdated = current;
                break;
            }
        }
        // ...if so we have, to update this one instead of adding a new item
        if (toBeUpdated) {
            for (int row = 0, count = item->childCount(); row < count; ++row)
                toBeUpdated->appendChild(new TestTreeItem(*item->childItem(row)));
            delete item;
        } else {
            parent->appendChild(item);
        }
    } else {
        parent->appendChild(item);
    }
657
658
659
    emit testTreeModelChanged();
}

660
void TestTreeModel::updateUnnamedQuickTest(const QString &mainFile,
661
                                           const QMap<QString, TestCodeLocationAndType> &functions)
662
{
663
664
    if (functions.isEmpty())
        return;
665

666
    if (!hasUnnamedQuickTests())
Christian Stenger's avatar
Christian Stenger committed
667
        addTestTreeItem(new TestTreeItem(QString(), QString(), TestTreeItem::TestClass), QuickTest);
668
669

    TestTreeItem *unnamed = unnamedQuickTests();
670
671
    foreach (const QString &functionName, functions.keys()) {
        const TestCodeLocationAndType locationAndType = functions.value(functionName);
672
        TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name,
673
                                                      locationAndType.m_type);
674
675
676
        testFunction->setLine(locationAndType.m_line);
        testFunction->setColumn(locationAndType.m_column);
        testFunction->setMainFile(mainFile);
677
        unnamed->appendChild(testFunction);
678
    }
679
680
}

681
void TestTreeModel::modifyTestTreeItem(TestTreeItem *item, TestTreeModel::Type type, const QStringList &files)
682
{
683
684
    QModelIndex index = rootIndexForType(type);
    TestTreeItem *parent = rootItemForType(type);
685
    if (files.isEmpty()) {
686
        if (TestTreeItem *unnamed = unnamedQuickTests()) {
687
688
689
            if (unnamed == item) // no need to update or delete
                return;

690
            index = indexForItem(unnamed);
691
692
693
694
            modifyTestSubtree(index, item);
        }
    } else {
        for (int row = 0; row < parent->childCount(); ++row) {
695
            if (files.contains(parent->childItem(row)->filePath())) {
696
697
698
699
                index = index.child(row, 0);
                modifyTestSubtree(index, item);
                break;
            }
700
701
        }
    }
702
703
704
    // item was created as temporary, destroy it if it won't get destroyed by its parent
    if (!item->parent())
        delete item;
705
706
}

707
void TestTreeModel::removeAllTestItems()
708
{
709
    m_autoTestRootItem->removeChildren();
710
    m_quickTestRootItem->removeChildren();
711
    m_googleTestRootItem->removeChildren();
712
713
714
    emit testTreeModelChanged();
}

715
void TestTreeModel::removeTestTreeItems(const QString &filePath, Type type)
716
{
717
    bool removed = false;
718
719
720
721
722
723
724
    const TestTreeItem *rootItem = rootItemForType(type);
    for (int row = rootItem->childCount() - 1; row >= 0; --row) {
        TestTreeItem *childItem = rootItem->childItem(row);
        if (filePath == childItem->filePath()) {
            delete takeItem(childItem);
            removed = true;
        }
725
    }
726
727
728
    if (removed)
        emit testTreeModelChanged();
}
729

730
731
732
733
734
735
736
TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type)
{
    switch (type) {
    case AutoTest:
        return m_autoTestRootItem;
    case QuickTest:
        return m_quickTestRootItem;
737
738
    case GoogleTest:
        return m_googleTestRootItem;
739
740
741
742
743
744
745
746
747
748
749
    }
    QTC_ASSERT(false, return 0);
}

QModelIndex TestTreeModel::rootIndexForType(TestTreeModel::Type type)
{
    switch (type) {
    case AutoTest:
        return index(0, 0);
    case QuickTest:
        return index(1, 0);
750
751
    case GoogleTest:
        return index(2, 0);
752
753
    }
    QTC_ASSERT(false, return QModelIndex());
754
755
}

756
void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem *newItem)
757
{
Christian Stenger's avatar
Christian Stenger committed
758
759
    if (!toBeModifiedIndex.isValid())
        return;
760

761
    TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(itemForIndex(toBeModifiedIndex));
762
    if (toBeModifiedItem->modifyContent(newItem))
763
764
        emit dataChanged(toBeModifiedIndex, toBeModifiedIndex,
                         QVector<int>() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole);
Christian Stenger's avatar
Christian Stenger committed
765
766

    // process sub-items as well...
767
    const int childCount = toBeModifiedItem->childCount();
768
    const int newChildCount = newItem->childCount();
Christian Stenger's avatar
Christian Stenger committed
769
770

    // for keeping the CheckState on modifications
771
772
    // TODO might still fail for duplicate entries
    QHash<QString, Qt::CheckState> checkStates;
Christian Stenger's avatar
Christian Stenger committed
773
    for (int row = 0; row < childCount; ++row) {
774
        const TestTreeItem *child = toBeModifiedItem->childItem(row);
775
        checkStates.insert(child->name(), child->checked());
Christian Stenger's avatar
Christian Stenger committed
776
777
778
    }

    if (childCount <= newChildCount) {
779
780
781
        processChildren(toBeModifiedIndex, newItem, childCount, checkStates);
        // add additional items
        for (int row = childCount; row < newChildCount; ++row) {
782
            const TestTreeItem *newChild = newItem->childItem(row);
783
784
785
786
787
            TestTreeItem *toBeAdded = new TestTreeItem(*newChild);
            if (checkStates.contains(toBeAdded->name())
                    && checkStates.value(toBeAdded->name()) != Qt::Checked)
                toBeAdded->setChecked(checkStates.value(toBeAdded->name()));
            toBeModifiedItem->appendChild(toBeAdded);
Christian Stenger's avatar
Christian Stenger committed
788
789
        }
    } else {
790
791
        processChildren(toBeModifiedIndex, newItem, newChildCount, checkStates);
        // remove rest of the items
792
793
        for (int row = childCount - 1; row > newChildCount; --row)
            delete takeItem(toBeModifiedItem->childItem(row));
Christian Stenger's avatar
Christian Stenger committed
794
    }
795
    emit testTreeModelChanged();
Christian Stenger's avatar
Christian Stenger committed
796
797
}

798
void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem *newItem,
799
800
801
802
                                    const int upperBound,
                                    const QHash<QString, Qt::CheckState> &checkStates)
{
    static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole
803
804
805
                                                           << Qt::ToolTipRole
                                                           << LinkRole;
    TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(itemForIndex(parentIndex));
806
807
    for (int row = 0; row < upperBound; ++row) {
        QModelIndex child = parentIndex.child(row, 0);
808
809
        TestTreeItem *toBeModifiedChild = toBeModifiedItem->childItem(row);
        const TestTreeItem *modifiedChild = newItem->childItem(row);
810
811
        if (toBeModifiedChild->modifyContent(modifiedChild))
            emit dataChanged(child, child, modificationRoles);
812
813
814
815
816
817

        // handle data tags - just remove old and add them
        if (modifiedChild->childCount() || toBeModifiedChild->childCount()) {
            toBeModifiedChild->removeChildren();
            const int count = modifiedChild->childCount();
            for (int childRow = 0; childRow < count; ++childRow)
818
                toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->childItem(childRow)));
819
820
        }

821
822
823
824
825
826
827
828
829
830
831
832
833
        if (checkStates.contains(toBeModifiedChild->name())) {
                Qt::CheckState state = checkStates.value(toBeModifiedChild->name());
                if (state != toBeModifiedChild->checked()) {
                    toBeModifiedChild->setChecked(state);
                    emit dataChanged(child, child, QVector<int>() << Qt::CheckStateRole);
            }
        } else { // newly added (BAD: happens for renaming as well)
            toBeModifiedChild->setChecked(Qt::Checked);
            emit dataChanged(child, child, QVector<int>() << Qt::CheckStateRole);
        }
    }
}

834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
#ifdef WITH_TESTS
int TestTreeModel::autoTestsCount() const
{
    return m_autoTestRootItem ? m_autoTestRootItem->childCount() : 0;
}

int TestTreeModel::namedQuickTestsCount() const
{
    return m_quickTestRootItem
            ? m_quickTestRootItem->childCount() - (hasUnnamedQuickTests() ? 1 : 0)
            : 0;
}

int TestTreeModel::unnamedQuickTestsCount() const
{
    if (TestTreeItem *unnamed = unnamedQuickTests())
        return unnamed->childCount();
    return 0;
}
853
854
855
856
857
858
859
860
861
862
863

int TestTreeModel::dataTagsCount() const
{
    int dataTagCount = 0;
    foreach (Utils::TreeItem *item, m_autoTestRootItem->children()) {
        TestTreeItem *classItem = static_cast<TestTreeItem *>(item);
        foreach (Utils::TreeItem *functionItem, classItem->children())
            dataTagCount += functionItem->childCount();
   }
    return dataTagCount;
}
864
865
866
867
868
869

int TestTreeModel::gtestNamesCount() const
{
    return m_googleTestRootItem ? m_googleTestRootItem->childCount() : 0;
}

870
QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const
871
{
872
    QMultiMap<QString, int> result;
873
874
875
876
877
878
879
880
881

    if (m_googleTestRootItem) {
        for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) {
            const TestTreeItem *current = m_googleTestRootItem->childItem(row);
            result.insert(current->name(), current->childCount());
        }
    }
    return result;
}
882
883
#endif

884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
/***************************** Sort/Filter Model **********************************/

TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent)
    : QSortFilterProxyModel(parent),
      m_sourceModel(sourceModel),
      m_sortMode(Alphabetically),
      m_filterMode(Basic)
{
    setSourceModel(sourceModel);
}

void TestTreeSortFilterModel::setSortMode(SortMode sortMode)
{
    m_sortMode = sortMode;
    invalidate();
}

void TestTreeSortFilterModel::setFilterMode(FilterMode filterMode)
{
    m_filterMode = filterMode;
    invalidateFilter();
}

void TestTreeSortFilterModel::toggleFilter(FilterMode filterMode)
{
    m_filterMode = toFilterMode(m_filterMode ^ filterMode);
    invalidateFilter();
}

TestTreeSortFilterModel::FilterMode TestTreeSortFilterModel::toFilterMode(int f)
{
    switch (f) {
    case TestTreeSortFilterModel::ShowInitAndCleanup:
        return TestTreeSortFilterModel::ShowInitAndCleanup;
    case TestTreeSortFilterModel::ShowTestData:
        return TestTreeSortFilterModel::ShowTestData;
    case TestTreeSortFilterModel::ShowAll:
        return TestTreeSortFilterModel::ShowAll;
    default:
        return TestTreeSortFilterModel::Basic;
    }
}

bool TestTreeSortFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
    // root items keep the intended order: 1st Auto Tests, 2nd Quick Tests
    const TestTreeItem *leftItem = static_cast<TestTreeItem *>(left.internalPointer());
Christian Stenger's avatar
Christian Stenger committed
931
    if (leftItem->type() == TestTreeItem::Root)
932
933
        return left.row() > right.row();

934
935
    const QString leftVal = m_sourceModel->data(left, Qt::DisplayRole).toString();
    const QString rightVal = m_sourceModel->data(right, Qt::DisplayRole).toString();
936
937
938
939
940
941
942
943
944

    // unnamed Quick Tests will always be listed first
    if (leftVal == tr(Constants::UNNAMED_QUICKTESTS))
        return false;
    if (rightVal == tr(Constants::UNNAMED_QUICKTESTS))
        return true;

    switch (m_sortMode) {
    case Alphabetically:
945
946
        if (leftVal == rightVal)
            return left.row() > right.row();
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
        return leftVal > rightVal;
    case Naturally: {
        const TextEditor::TextEditorWidget::Link leftLink =
                m_sourceModel->data(left, LinkRole).value<TextEditor::TextEditorWidget::Link>();
        const TextEditor::TextEditorWidget::Link rightLink =
                m_sourceModel->data(right, LinkRole).value<TextEditor::TextEditorWidget::Link>();

        if (leftLink.targetFileName == rightLink.targetFileName) {
            return leftLink.targetLine == rightLink.targetLine
                    ? leftLink.targetColumn > rightLink.targetColumn
                    : leftLink.targetLine > rightLink.targetLine;
        } else {
            return leftLink.targetFileName > rightLink.targetFileName;
        }
    }
    default:
        return true;
    }
}

bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    QModelIndex index = m_sourceModel->index(sourceRow, 0,sourceParent);
    if (!index.isValid())
        return false;

973
    const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
974

975
    switch (item->type()) {
Christian Stenger's avatar
Christian Stenger committed
976
    case TestTreeItem::TestDataFunction:
977
        return m_filterMode & ShowTestData;
Christian Stenger's avatar
Christian Stenger committed
978
    case TestTreeItem::TestSpecialFunction:
979
980
981
982
        return m_filterMode & ShowInitAndCleanup;
    default:
        return true;
    }
983
984
}

Christian Stenger's avatar
Christian Stenger committed
985
986
} // namespace Internal
} // namespace Autotest