testtreemodel.cpp 35.9 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
45
namespace Autotest {
namespace Internal {

TestTreeModel::TestTreeModel(QObject *parent) :
46
    TreeModel(parent),
Christian Stenger's avatar
Christian Stenger committed
47
48
    m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::Root)),
    m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::Root)),
49
    m_googleTestRootItem(new TestTreeItem(tr("Google Tests"), QString(), TestTreeItem::Root)),
50
    m_parser(new TestCodeParser(this)),
51
    m_connectionsInitialized(false)
Christian Stenger's avatar
Christian Stenger committed
52
{
53
54
    rootItem()->appendChild(m_autoTestRootItem);
    rootItem()->appendChild(m_quickTestRootItem);
55
    rootItem()->appendChild(m_googleTestRootItem);
56

57
58
59
60
61
62
63
64
    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);
65
    connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated,
66
            this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection);
67
    connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved,
68
            this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection);
69
70
    connect(m_parser, &TestCodeParser::gTestsRemoved,
            this, &TestTreeModel::removeGTests, Qt::QueuedConnection);
71

Christian Stenger's avatar
Christian Stenger committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//    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;
}

100
101
void TestTreeModel::enableParsing()
{
102
    m_refCounter.ref();
103
104
105
106

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

107
    m_parser->setState(TestCodeParser::Idle);
108
109
110
111
112
    if (m_connectionsInitialized)
        return;

    ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance();
    connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged,
113
            m_parser, &TestCodeParser::onStartupProjectChanged);
114
115
116
117
118
119

    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);
120
121
    connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated,
            m_parser, &TestCodeParser::onProjectPartsUpdated);
122
123
124
125
126
127
128
129
130
131
132

    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()
{
133
134
    if (!m_refCounter.deref())
        m_parser->setState(TestCodeParser::Disabled);
135
136
}

Christian Stenger's avatar
Christian Stenger committed
137
138
139
140
141
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

142
143
144
145
146
    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
147
            case TestTreeItem::TestClass:
148
            case TestTreeItem::GTestCase:
149
            case TestTreeItem::GTestCaseParameterized:
150
                if (item->childCount() > 0)
Christian Stenger's avatar
Christian Stenger committed
151
152
                    emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
                break;
Christian Stenger's avatar
Christian Stenger committed
153
            case TestTreeItem::TestFunction:
154
            case TestTreeItem::GTestName:
Christian Stenger's avatar
Christian Stenger committed
155
156
157
158
159
160
                emit dataChanged(index.parent(), index.parent());
                break;
            default: // avoid warning regarding unhandled enum member
                break;
            }
        }
161
        return true;
Christian Stenger's avatar
Christian Stenger committed
162
163
164
165
166
167
168
169
170
    }
    return false;
}

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

171
    TestTreeItem *item = static_cast<TestTreeItem *>(itemForIndex(index));
Christian Stenger's avatar
Christian Stenger committed
172
    switch(item->type()) {
Christian Stenger's avatar
Christian Stenger committed
173
    case TestTreeItem::TestClass:
174
    case TestTreeItem::GTestCase:
175
    case TestTreeItem::GTestCaseParameterized:
176
        if (item->name().isEmpty())
177
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
178
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
179
    case TestTreeItem::TestFunction:
180
    case TestTreeItem::GTestName:
181
        if (item->parentItem()->name().isEmpty())
182
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
183
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
184
    case TestTreeItem::Root:
Christian Stenger's avatar
Christian Stenger committed
185
        return Qt::ItemIsEnabled;
Christian Stenger's avatar
Christian Stenger committed
186
187
188
    case TestTreeItem::TestDataFunction:
    case TestTreeItem::TestSpecialFunction:
    case TestTreeItem::TestDataTag:
189
190
    default:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
191
192
193
194
195
    }
}

bool TestTreeModel::hasTests() const
{
196
197
    return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0
            || m_googleTestRootItem->childCount() > 0;
Christian Stenger's avatar
Christian Stenger committed
198
199
}

200
201
202
203
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
    QList<TestConfiguration *> result;

204
205
206
207
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

208
209
    // get all Auto Tests
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
210
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
211

212
213
        TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(),
                                                      child->childCount());
214
215
        tc->setMainFilePath(child->filePath());
        tc->setProject(project);
216
217
        result << tc;
    }
218
219
220
221

    // get all Quick Tests
    QMap<QString, int> foundMains;
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
222
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
223
224
225
        // unnamed Quick Tests must be handled separately
        if (child->name().isEmpty()) {
            for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) {
226
                const TestTreeItem *grandChild = child->childItem(childRow);
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
                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));
243
244
        tc->setMainFilePath(mainFile);
        tc->setProject(project);
245
246
247
        result << tc;
    }

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    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);
265
        tc->setTestType(TestTypeGTest);
266
267
268
        result << tc;
    }

269
270
271
272
273
274
    return result;
}

QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
    QList<TestConfiguration *> result;
275
276
277
278
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

279
    TestConfiguration *testConfiguration = 0;
280

281
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
282
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
283
284
285
286
287

        switch (child->checked()) {
        case Qt::Unchecked:
            continue;
        case Qt::Checked:
288
            testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount());
289
290
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
291
            result << testConfiguration;
292
293
294
            continue;
        case Qt::PartiallyChecked:
        default:
295
            const QString childName = child->name();
296
297
298
            int grandChildCount = child->childCount();
            QStringList testCases;
            for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
299
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
300
301
302
303
                if (grandChild->checked() == Qt::Checked)
                    testCases << grandChild->name();
            }

304
            testConfiguration = new TestConfiguration(childName, testCases);
305
306
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
307
            result << testConfiguration;
308
309
        }
    }
310
311
312
313
314
315
316
    // 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;

317
318
    if (TestTreeItem *unnamed = unnamedQuickTests()) {
        for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) {
319
            const TestTreeItem *grandChild = unnamed->childItem(childRow);
320
321
            const QString mainFile = grandChild->mainFile();
            if (foundMains.contains(mainFile)) {
322
323
324
                QTC_ASSERT(testConfiguration,
                           qWarning() << "Illegal state (unnamed Quick Test listed as named)";
                           return QList<TestConfiguration *>());
325
                foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1);
326
            } else {
327
328
329
                testConfiguration = new TestConfiguration(QString(), QStringList());
                testConfiguration->setTestCaseCount(1);
                testConfiguration->setUnnamedOnly(true);
330
331
                testConfiguration->setMainFilePath(mainFile);
                testConfiguration->setProject(project);
332
                foundMains.insert(mainFile, testConfiguration);
333
            }
334
335
336
337
        }
    }

    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
338
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
339
340
341
342
343
344
345
346
347
348
349
350
351
352
        // 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) {
353
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
Christian Stenger's avatar
Christian Stenger committed
354
                if (grandChild->type() != TestTreeItem::TestFunction)
355
                    continue;
356
                testFunctions << child->name() + QLatin1String("::") + grandChild->name();
357
358
359
360
361
362
363
364
365
            }
            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());
366
                    tc->setUnnamedOnly(false);
367
368
369
370
371
372
                } else {
                    oldFunctions << testFunctions;
                    tc->setTestCases(oldFunctions);
                }
            } else {
                tc = new TestConfiguration(QString(), testFunctions);
373
374
                tc->setMainFilePath(child->mainFile());
                tc->setProject(project);
375
376
377
378
379
380
381
                foundMains.insert(child->mainFile(), tc);
            }
            break;
        }
    }

    foreach (TestConfiguration *config, foundMains.values())
382
383
        if (!config->unnamedOnly())
            result << config;
384

385
386
387
388
389
390
391
392
393
394
395
396
397
    // 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);
398
399
400
401
402
403
404
405
            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;
            }
406
407
408
409
410
411
412
            proFilesWithEnabledTestSets.insert(proFile, enabled);
        }
    }

    foreach (const QString &proFile, proFilesWithEnabledTestSets.keys()) {
        TestConfiguration *tc = new TestConfiguration(QString(),
                                                      proFilesWithEnabledTestSets.value(proFile));
413
        tc->setTestType(TestTypeGTest);
414
415
416
417
418
        tc->setProFile(proFile);
        tc->setProject(project);
        result << tc;
    }

419
420
421
    return result;
}

422
423
424
425
426
427
428
429
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
430
    case TestTreeItem::TestClass: {
431
432
433
434
        if (item->parent() == m_quickTestRootItem) {
            // Quick Test TestCase
            QStringList testFunctions;
            for (int row = 0, count = item->childCount(); row < count; ++row) {
435
436
                    testFunctions << item->name() + QLatin1String("::")
                                     + item->childItem(row)->name();
437
438
439
440
441
442
443
444
445
446
447
448
            }
            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
449
    case TestTreeItem::TestFunction: {
450
        const TestTreeItem *parent = item->parentItem();
451
452
453
454
455
456
457
458
459
460
461
462
463
464
        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
465
    case TestTreeItem::TestDataTag: {
466
467
        const TestTreeItem *function = item->parentItem();
        const TestTreeItem *parent = function ? function->parentItem() : 0;
468
469
470
471
472
473
474
475
        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;
    }
476
477
478
479
480
481
    case TestTreeItem::GTestCase:
    case TestTreeItem::GTestCaseParameterized: {
        QString testSpecifier = item->name() + QLatin1String(".*");
        if (item->type() == TestTreeItem::GTestCaseParameterized)
            testSpecifier.prepend(QLatin1String("*/"));

482
        if (int childCount = item->childCount()) {
483
            config = new TestConfiguration(QString(), QStringList(testSpecifier));
484
485
486
            config->setTestCaseCount(childCount);
            config->setProFile(item->childItem(0)->mainFile());
            config->setProject(project);
487
            config->setTestType(TestTypeGTest);
488
489
490
491
492
        }
        break;
    }
    case TestTreeItem::GTestName: {
        const TestTreeItem *parent = item->parentItem();
493
494
495
496
497
498
499
        QString testSpecifier = parent->name() + QLatin1Char('.') + item->name();

        if (parent->type() == TestTreeItem::GTestCaseParameterized) {
            testSpecifier.prepend(QLatin1String("*/"));
            testSpecifier.append(QLatin1String("/*"));
        }
        config = new TestConfiguration(QString(), QStringList(testSpecifier));
500
501
        config->setProFile(item->mainFile());
        config->setProject(project);
502
        config->setTestType(TestTypeGTest);
503
504
        break;
    }
505
506
507
508
509
510
511
    // not supported items
    default:
        return 0;
    }
    return config;
}

512
513
514
QString TestTreeModel::getMainFileForUnnamedQuickTest(const QString &qmlFile) const
{
    const TestTreeItem *unnamed = unnamedQuickTests();
Christian Stenger's avatar
Christian Stenger committed
515
516
    const int count = unnamed ? unnamed->childCount() : 0;
    for (int row = 0; row < count; ++row) {
517
        const TestTreeItem *child = unnamed->childItem(row);
518
519
520
521
522
523
        if (qmlFile == child->filePath())
            return child->mainFile();
    }
    return QString();
}

524
void TestTreeModel::qmlFilesForMainFile(const QString &mainFile, QSet<QString> *filePaths) const
525
{
526
    const TestTreeItem *unnamed = unnamedQuickTests();
527
528
    if (!unnamed)
        return;
529
530
    for (int row = 0, count = unnamed->childCount(); row < count; ++row) {
        const TestTreeItem *child = unnamed->childItem(row);
531
        if (child->mainFile() == mainFile)
532
533
534
535
536
537
            filePaths->insert(child->filePath());
    }
}

QList<QString> TestTreeModel::getUnnamedQuickTestFunctions() const
{
538
    const TestTreeItem *unnamed = unnamedQuickTests();
539
540
541
542
543
544
545
546
    if (unnamed)
        return unnamed->getChildNames();
    return QList<QString>();
}

bool TestTreeModel::hasUnnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row)
547
        if (m_quickTestRootItem->childItem(row)->name().isEmpty())
548
549
550
551
            return true;
    return false;
}

552
553
554
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
555
        TestTreeItem *child = m_quickTestRootItem->childItem(row);
556
557
558
559
560
561
        if (child->name().isEmpty())
            return child;
    }
    return 0;
}

562
void TestTreeModel::removeUnnamedQuickTests(const QString &filePath)
Christian Stenger's avatar
Christian Stenger committed
563
{
564
565
566
    TestTreeItem *unnamedQT = unnamedQuickTests();
    if (!unnamedQT)
        return;
567

568
    for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) {
569
        TestTreeItem *child = unnamedQT->childItem(childRow);
570
        if (filePath == child->filePath())
571
            delete takeItem(child);
572
    }
573
574

    if (unnamedQT->childCount() == 0)
575
        delete takeItem(unnamedQT);
576
577
578
    emit testTreeModelChanged();
}

579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
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();
}

594
void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type)
595
{
596
    TestTreeItem *parent = rootItemForType(type);
597
598
599
600
601
    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);
602
            if (current->name() == item->name() && current->type() == item->type()) {
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
                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);
    }
618
619
620
    emit testTreeModelChanged();
}

621
void TestTreeModel::updateUnnamedQuickTest(const QString &mainFile,
622
                                           const QMap<QString, TestCodeLocationAndType> &functions)
623
{
624
625
    if (functions.isEmpty())
        return;
626

627
    if (!hasUnnamedQuickTests())
Christian Stenger's avatar
Christian Stenger committed
628
        addTestTreeItem(new TestTreeItem(QString(), QString(), TestTreeItem::TestClass), QuickTest);
629
630

    TestTreeItem *unnamed = unnamedQuickTests();
631
632
    foreach (const QString &functionName, functions.keys()) {
        const TestCodeLocationAndType locationAndType = functions.value(functionName);
633
        TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name,
634
                                                      locationAndType.m_type);
635
636
637
        testFunction->setLine(locationAndType.m_line);
        testFunction->setColumn(locationAndType.m_column);
        testFunction->setMainFile(mainFile);
638
        unnamed->appendChild(testFunction);
639
    }
640
641
}

642
void TestTreeModel::modifyTestTreeItem(TestTreeItem *item, TestTreeModel::Type type, const QStringList &files)
643
{
644
645
    QModelIndex index = rootIndexForType(type);
    TestTreeItem *parent = rootItemForType(type);
646
    if (files.isEmpty()) {
647
        if (TestTreeItem *unnamed = unnamedQuickTests()) {
648
649
650
            if (unnamed == item) // no need to update or delete
                return;

651
            index = indexForItem(unnamed);
652
653
654
655
            modifyTestSubtree(index, item);
        }
    } else {
        for (int row = 0; row < parent->childCount(); ++row) {
656
            if (files.contains(parent->childItem(row)->filePath())) {
657
658
659
660
                index = index.child(row, 0);
                modifyTestSubtree(index, item);
                break;
            }
661
662
        }
    }
663
664
665
    // item was created as temporary, destroy it if it won't get destroyed by its parent
    if (!item->parent())
        delete item;
666
667
}

668
void TestTreeModel::removeAllTestItems()
669
{
670
    m_autoTestRootItem->removeChildren();
671
    m_quickTestRootItem->removeChildren();
672
    m_googleTestRootItem->removeChildren();
673
674
675
    emit testTreeModelChanged();
}

676
void TestTreeModel::removeTestTreeItems(const QString &filePath, Type type)
677
{
678
    bool removed = false;
679
680
681
682
683
684
685
    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;
        }
686
    }
687
688
689
    if (removed)
        emit testTreeModelChanged();
}
690

691
692
693
694
695
696
697
TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type)
{
    switch (type) {
    case AutoTest:
        return m_autoTestRootItem;
    case QuickTest:
        return m_quickTestRootItem;
698
699
    case GoogleTest:
        return m_googleTestRootItem;
700
701
702
703
704
705
706
707
708
709
710
    }
    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);
711
712
    case GoogleTest:
        return index(2, 0);
713
714
    }
    QTC_ASSERT(false, return QModelIndex());
715
716
}

717
void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem *newItem)
718
{
Christian Stenger's avatar
Christian Stenger committed
719
720
    if (!toBeModifiedIndex.isValid())
        return;
721

722
    TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(itemForIndex(toBeModifiedIndex));
723
    if (toBeModifiedItem->modifyContent(newItem))
724
725
        emit dataChanged(toBeModifiedIndex, toBeModifiedIndex,
                         QVector<int>() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole);
Christian Stenger's avatar
Christian Stenger committed
726
727

    // process sub-items as well...
728
    const int childCount = toBeModifiedItem->childCount();
729
    const int newChildCount = newItem->childCount();
Christian Stenger's avatar
Christian Stenger committed
730
731

    // for keeping the CheckState on modifications
732
733
    // TODO might still fail for duplicate entries
    QHash<QString, Qt::CheckState> checkStates;
Christian Stenger's avatar
Christian Stenger committed
734
    for (int row = 0; row < childCount; ++row) {
735
        const TestTreeItem *child = toBeModifiedItem->childItem(row);
736
        checkStates.insert(child->name(), child->checked());
Christian Stenger's avatar
Christian Stenger committed
737
738
739
    }

    if (childCount <= newChildCount) {
740
741
742
        processChildren(toBeModifiedIndex, newItem, childCount, checkStates);
        // add additional items
        for (int row = childCount; row < newChildCount; ++row) {
743
            const TestTreeItem *newChild = newItem->childItem(row);
744
745
746
747
748
            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
749
750
        }
    } else {
751
752
        processChildren(toBeModifiedIndex, newItem, newChildCount, checkStates);
        // remove rest of the items
753
754
        for (int row = childCount - 1; row > newChildCount; --row)
            delete takeItem(toBeModifiedItem->childItem(row));
Christian Stenger's avatar
Christian Stenger committed
755
    }
756
    emit testTreeModelChanged();
Christian Stenger's avatar
Christian Stenger committed
757
758
}

759
void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem *newItem,
760
761
762
763
                                    const int upperBound,
                                    const QHash<QString, Qt::CheckState> &checkStates)
{
    static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole
764
765
766
                                                           << Qt::ToolTipRole
                                                           << LinkRole;
    TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(itemForIndex(parentIndex));
767
768
    for (int row = 0; row < upperBound; ++row) {
        QModelIndex child = parentIndex.child(row, 0);
769
770
        TestTreeItem *toBeModifiedChild = toBeModifiedItem->childItem(row);
        const TestTreeItem *modifiedChild = newItem->childItem(row);
771
772
        if (toBeModifiedChild->modifyContent(modifiedChild))
            emit dataChanged(child, child, modificationRoles);
773
774
775
776
777
778

        // 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)
779
                toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->childItem(childRow)));
780
781
        }

782
783
784
785
786
787
788
789
790
791
792
793
794
        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);
        }
    }
}

795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
#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;
}
814
815
816
817
818
819
820
821
822
823
824

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;
}
825
826
827
828
829
830

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

831
QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const
832
{
833
    QMultiMap<QString, int> result;
834
835
836
837
838
839
840
841
842

    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;
}
843
844
#endif

845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
/***************************** 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
892
    if (leftItem->type() == TestTreeItem::Root)
893
894
        return left.row() > right.row();

895
896
    const QString leftVal = m_sourceModel->data(left, Qt::DisplayRole).toString();
    const QString rightVal = m_sourceModel->data(right, Qt::DisplayRole).toString();
897
898
899
900
901
902
903
904
905

    // 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:
906
907
        if (leftVal == rightVal)
            return left.row() > right.row();
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
        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;

934
    const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
935

936
    switch (item->type()) {
Christian Stenger's avatar
Christian Stenger committed
937
    case TestTreeItem::TestDataFunction:
938
        return m_filterMode & ShowTestData;
Christian Stenger's avatar
Christian Stenger committed
939
    case TestTreeItem::TestSpecialFunction:
940
941
942
943
        return m_filterMode & ShowInitAndCleanup;
    default:
        return true;
    }
944
945
}

Christian Stenger's avatar
Christian Stenger committed
946
947
} // namespace Internal
} // namespace Autotest