testtreemodel.cpp 35.3 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
    connect(m_parser, &TestCodeParser::aboutToPerformFullParse, this,
90 91 92
            &TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
    connect(m_parser, &TestCodeParser::testItemCreated,
            this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection);
93 94 95 96
    connect(m_parser, &TestCodeParser::parsingFinished,
            this, &TestTreeModel::sweep, Qt::QueuedConnection);
    connect(m_parser, &TestCodeParser::parsingFailed,
            this, &TestTreeModel::sweep, Qt::QueuedConnection);
Christian Stenger's avatar
Christian Stenger committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
}

static TestTreeModel *m_instance = 0;

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

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

113 114
void TestTreeModel::enableParsing()
{
115
    m_refCounter.ref();
116 117 118 119

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

120
    m_parser->setState(TestCodeParser::Idle);
121 122 123 124 125
    if (m_connectionsInitialized)
        return;

    ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance();
    connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged,
126
            m_parser, &TestCodeParser::onStartupProjectChanged);
127 128 129 130 131

    CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
    connect(cppMM, &CppTools::CppModelManager::documentUpdated,
            m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
    connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles,
132
            this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
133 134
    connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated,
            m_parser, &TestCodeParser::onProjectPartsUpdated);
135 136 137 138 139

    QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance();
    connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
            m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
    connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
140
            this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
141 142 143 144 145
    m_connectionsInitialized = true;
}

void TestTreeModel::disableParsing()
{
146 147
    if (!m_refCounter.deref())
        m_parser->setState(TestCodeParser::Disabled);
148 149
}

Christian Stenger's avatar
Christian Stenger committed
150 151 152 153 154
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

155 156 157 158 159
    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
160
            case TestTreeItem::TestClass:
161
            case TestTreeItem::GTestCase:
162
            case TestTreeItem::GTestCaseParameterized:
163
                if (item->childCount() > 0)
Christian Stenger's avatar
Christian Stenger committed
164 165
                    emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
                break;
Christian Stenger's avatar
Christian Stenger committed
166
            case TestTreeItem::TestFunction:
167
            case TestTreeItem::GTestName:
Christian Stenger's avatar
Christian Stenger committed
168 169 170 171 172 173
                emit dataChanged(index.parent(), index.parent());
                break;
            default: // avoid warning regarding unhandled enum member
                break;
            }
        }
174
        return true;
Christian Stenger's avatar
Christian Stenger committed
175 176 177 178 179 180 181 182 183
    }
    return false;
}

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

184
    TestTreeItem *item = static_cast<TestTreeItem *>(itemForIndex(index));
Christian Stenger's avatar
Christian Stenger committed
185
    switch(item->type()) {
Christian Stenger's avatar
Christian Stenger committed
186
    case TestTreeItem::TestClass:
187
    case TestTreeItem::GTestCase:
188
    case TestTreeItem::GTestCaseParameterized:
189
        if (item->name().isEmpty())
190
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
191
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
192
    case TestTreeItem::TestFunction:
193
    case TestTreeItem::GTestName:
194
        if (item->parentItem()->name().isEmpty())
195
            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
196
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
Christian Stenger's avatar
Christian Stenger committed
197
    case TestTreeItem::Root:
Christian Stenger's avatar
Christian Stenger committed
198
        return Qt::ItemIsEnabled;
Christian Stenger's avatar
Christian Stenger committed
199 200 201
    case TestTreeItem::TestDataFunction:
    case TestTreeItem::TestSpecialFunction:
    case TestTreeItem::TestDataTag:
202 203
    default:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
Christian Stenger's avatar
Christian Stenger committed
204 205 206 207 208
    }
}

bool TestTreeModel::hasTests() const
{
209 210
    return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0
            || m_googleTestRootItem->childCount() > 0;
Christian Stenger's avatar
Christian Stenger committed
211 212
}

213 214 215 216
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
    QList<TestConfiguration *> result;

217 218 219 220
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

221 222
    // get all Auto Tests
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
223
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
224

225 226
        TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(),
                                                      child->childCount());
227 228
        tc->setMainFilePath(child->filePath());
        tc->setProject(project);
229 230
        result << tc;
    }
231 232 233 234

    // get all Quick Tests
    QMap<QString, int> foundMains;
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
235
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
236 237 238
        // unnamed Quick Tests must be handled separately
        if (child->name().isEmpty()) {
            for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) {
239
                const TestTreeItem *grandChild = child->childItem(childRow);
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
                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));
256 257
        tc->setMainFilePath(mainFile);
        tc->setProject(project);
258 259 260
        result << tc;
    }

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
    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);
278
        tc->setTestType(TestTypeGTest);
279 280 281
        result << tc;
    }

282 283 284 285 286 287
    return result;
}

QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
    QList<TestConfiguration *> result;
288 289 290 291
    ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
    if (!project)
        return result;

292
    TestConfiguration *testConfiguration = 0;
293

294
    for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
295
        const TestTreeItem *child = m_autoTestRootItem->childItem(row);
296 297 298 299 300

        switch (child->checked()) {
        case Qt::Unchecked:
            continue;
        case Qt::Checked:
301
            testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount());
302 303
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
304
            result << testConfiguration;
305 306 307
            continue;
        case Qt::PartiallyChecked:
        default:
308
            const QString childName = child->name();
309 310 311
            int grandChildCount = child->childCount();
            QStringList testCases;
            for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
312
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
313 314 315 316
                if (grandChild->checked() == Qt::Checked)
                    testCases << grandChild->name();
            }

317
            testConfiguration = new TestConfiguration(childName, testCases);
318 319
            testConfiguration->setMainFilePath(child->filePath());
            testConfiguration->setProject(project);
320
            result << testConfiguration;
321 322
        }
    }
323 324 325 326 327 328 329
    // 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;

330 331
    if (TestTreeItem *unnamed = unnamedQuickTests()) {
        for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) {
332
            const TestTreeItem *grandChild = unnamed->childItem(childRow);
333 334
            const QString mainFile = grandChild->mainFile();
            if (foundMains.contains(mainFile)) {
335 336 337
                QTC_ASSERT(testConfiguration,
                           qWarning() << "Illegal state (unnamed Quick Test listed as named)";
                           return QList<TestConfiguration *>());
338
                foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1);
339
            } else {
340 341 342
                testConfiguration = new TestConfiguration(QString(), QStringList());
                testConfiguration->setTestCaseCount(1);
                testConfiguration->setUnnamedOnly(true);
343 344
                testConfiguration->setMainFilePath(mainFile);
                testConfiguration->setProject(project);
345
                foundMains.insert(mainFile, testConfiguration);
346
            }
347 348 349 350
        }
    }

    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
351
        const TestTreeItem *child = m_quickTestRootItem->childItem(row);
352 353 354 355 356 357 358 359 360 361 362 363 364 365
        // 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) {
366
                const TestTreeItem *grandChild = child->childItem(grandChildRow);
Christian Stenger's avatar
Christian Stenger committed
367
                if (grandChild->type() != TestTreeItem::TestFunction)
368
                    continue;
369
                testFunctions << child->name() + QLatin1String("::") + grandChild->name();
370 371 372 373 374 375 376 377 378
            }
            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());
379
                    tc->setUnnamedOnly(false);
380 381 382 383 384 385
                } else {
                    oldFunctions << testFunctions;
                    tc->setTestCases(oldFunctions);
                }
            } else {
                tc = new TestConfiguration(QString(), testFunctions);
386 387
                tc->setMainFilePath(child->mainFile());
                tc->setProject(project);
388 389 390 391 392 393 394
                foundMains.insert(child->mainFile(), tc);
            }
            break;
        }
    }

    foreach (TestConfiguration *config, foundMains.values())
395 396
        if (!config->unnamedOnly())
            result << config;
397

398 399 400 401 402 403 404 405 406 407 408 409 410
    // 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);
411 412 413 414 415 416 417 418
            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;
            }
419 420 421 422 423 424 425
            proFilesWithEnabledTestSets.insert(proFile, enabled);
        }
    }

    foreach (const QString &proFile, proFilesWithEnabledTestSets.keys()) {
        TestConfiguration *tc = new TestConfiguration(QString(),
                                                      proFilesWithEnabledTestSets.value(proFile));
426
        tc->setTestType(TestTypeGTest);
427 428 429 430 431
        tc->setProFile(proFile);
        tc->setProject(project);
        result << tc;
    }

432 433 434
    return result;
}

435 436 437 438 439 440 441 442
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
443
    case TestTreeItem::TestClass: {
444 445 446 447
        if (item->parent() == m_quickTestRootItem) {
            // Quick Test TestCase
            QStringList testFunctions;
            for (int row = 0, count = item->childCount(); row < count; ++row) {
448 449
                    testFunctions << item->name() + QLatin1String("::")
                                     + item->childItem(row)->name();
450 451 452 453 454 455 456 457 458 459 460 461
            }
            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
462
    case TestTreeItem::TestFunction: {
463
        const TestTreeItem *parent = item->parentItem();
464 465 466 467 468 469 470 471 472 473 474 475 476 477
        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
478
    case TestTreeItem::TestDataTag: {
479 480
        const TestTreeItem *function = item->parentItem();
        const TestTreeItem *parent = function ? function->parentItem() : 0;
481 482 483 484 485 486 487 488
        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;
    }
489 490 491 492 493 494
    case TestTreeItem::GTestCase:
    case TestTreeItem::GTestCaseParameterized: {
        QString testSpecifier = item->name() + QLatin1String(".*");
        if (item->type() == TestTreeItem::GTestCaseParameterized)
            testSpecifier.prepend(QLatin1String("*/"));

495
        if (int childCount = item->childCount()) {
496
            config = new TestConfiguration(QString(), QStringList(testSpecifier));
497 498 499
            config->setTestCaseCount(childCount);
            config->setProFile(item->childItem(0)->mainFile());
            config->setProject(project);
500
            config->setTestType(TestTypeGTest);
501 502 503 504 505
        }
        break;
    }
    case TestTreeItem::GTestName: {
        const TestTreeItem *parent = item->parentItem();
506 507 508 509 510 511 512
        QString testSpecifier = parent->name() + QLatin1Char('.') + item->name();

        if (parent->type() == TestTreeItem::GTestCaseParameterized) {
            testSpecifier.prepend(QLatin1String("*/"));
            testSpecifier.append(QLatin1String("/*"));
        }
        config = new TestConfiguration(QString(), QStringList(testSpecifier));
513 514
        config->setProFile(item->mainFile());
        config->setProject(project);
515
        config->setTestType(TestTypeGTest);
516 517
        break;
    }
518 519 520 521 522 523 524
    // not supported items
    default:
        return 0;
    }
    return config;
}

525 526 527
bool TestTreeModel::hasUnnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row)
528
        if (m_quickTestRootItem->childItem(row)->name().isEmpty())
529 530 531 532
            return true;
    return false;
}

533 534 535
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
    for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
536
        TestTreeItem *child = m_quickTestRootItem->childItem(row);
537 538 539 540 541 542
        if (child->name().isEmpty())
            return child;
    }
    return 0;
}

543
void TestTreeModel::removeFiles(const QStringList &files)
Christian Stenger's avatar
Christian Stenger committed
544
{
545 546 547
    foreach (const QString &file, files)
        markForRemoval(file);
    sweep();
548 549
}

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
void TestTreeModel::markAllForRemoval()
{
    foreach (Utils::TreeItem *item, m_autoTestRootItem->children())
        static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);

    foreach (Utils::TreeItem *item, m_quickTestRootItem->children())
        static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);

    foreach (Utils::TreeItem *item, m_googleTestRootItem->children())
        static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);
}

void TestTreeModel::markForRemoval(const QString &filePath)
{
    if (filePath.isEmpty())
        return;

    Type types[] = { AutoTest, QuickTest, GoogleTest };
    for (Type type : types) {
        TestTreeItem *root = rootItemForType(type);
        for (int childRow = root->childCount() - 1; childRow >= 0; --childRow) {
            TestTreeItem *child = root->childItem(childRow);
            if (child->markedForRemoval())
                continue;
            // Qt + named Quick Tests
            if (child->filePath() == filePath || child->referencingFile() == filePath) {
                child->markForRemovalRecursively(true);
            } else {
                // unnamed Quick Tests and GTest and Qt Tests with separated source/header
                int grandChildRow = child->childCount() - 1;
                for ( ; grandChildRow >= 0; --grandChildRow) {
                    TestTreeItem *grandChild = child->childItem(grandChildRow);
                    if (grandChild->filePath() == filePath
                            || grandChild->referencingFile() == filePath) {
                        grandChild->markForRemovalRecursively(true);
                    }
                }
            }
        }
    }
}

void TestTreeModel::sweep()
{
    bool hasChanged = false;
    Type types[] = { AutoTest, QuickTest, GoogleTest };
    for (Type type : types) {
        TestTreeItem *root = rootItemForType(type);
        hasChanged |= sweepChildren(root);
    }
    if (hasChanged)
        emit testTreeModelChanged();
}

/**
 * @note after calling this function emit testTreeModelChanged() if it returns true
 */
bool TestTreeModel::sweepChildren(TestTreeItem *item)
{
    bool hasChanged = false;
    for (int row = item->childCount() - 1; row >= 0; --row) {
        TestTreeItem *child = item->childItem(row);

        if (child->parentItem()->type() != TestTreeItem::Root && child->markedForRemoval()) {
            delete takeItem(child);
            hasChanged = true;
            continue;
        }
        if (bool noEndNode = child->hasChildren()) {
            hasChanged |= sweepChildren(child);
            if (noEndNode && child->childCount() == 0) {
                delete takeItem(child);
                hasChanged = true;
                continue;
            }
        }
        child->markForRemoval(false);
    }
    return hasChanged;
}

631 632 633 634 635 636 637
QMap<QString, QString> TestTreeModel::referencingFiles() const
{
    ReferencingFilesFinder finder;
    rootItem()->walkTree(&finder);
    return finder.referencingFiles();
}

638 639
TestTreeItem *TestTreeModel::findTestTreeItemByContent(TestTreeItem *item, TestTreeItem *parent,
                                                       Type type)
640
{
641 642 643 644
    for (int row = 0, count = parent->childCount(); row < count; ++row) {
        TestTreeItem *current = parent->childItem(row);
        if (current->name() != item->name())
            continue;
645

646 647 648 649 650 651 652 653 654 655 656 657 658 659
        switch (type) {
        case AutoTest:
            if (current->filePath() == item->filePath())
                return current;
            break;
        case QuickTest:
            if (current->filePath() == item->filePath() && current->mainFile() == item->mainFile())
                return current;
            break;
        case GoogleTest:
            if (current->type() == item->type())
                return current;
            break;
        }
660
    }
661
    return 0;
662 663
}

664
void TestTreeModel::addTestTreeItem(TestTreeItem *item, Type type)
665
{
666
    TestTreeItem *parent = rootItemForType(type);
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    TestTreeItem *toBeUpdated = findTestTreeItemByContent(item, parent, type);
    const int count = item->childCount();
    if (toBeUpdated) {
        if (!toBeUpdated->markedForRemoval()) {
            for (int row = 0; row < count; ++row)
                toBeUpdated->appendChild(new TestTreeItem(*item->childItem(row)));
        } else {
            for (int childRow = count - 1; childRow >= 0; --childRow) {
                TestTreeItem *childItem = item->childItem(childRow);
                TestTreeItem *origChild = findTestTreeItemByContent(childItem, toBeUpdated, type);
                if (origChild) {
                    QModelIndex toBeModifiedIndex = indexForItem(origChild);
                    modifyTestSubtree(toBeModifiedIndex, childItem);
                } else {
                    toBeUpdated->insertChild(qMin(count, toBeUpdated->childCount()),
                                             new TestTreeItem(*childItem));
                }
684
            }
685
        }
686
        delete item;
687 688 689 690
    } else {
        parent->appendChild(item);
    }
    emit testTreeModelChanged();
691 692
}

693
void TestTreeModel::removeAllTestItems()
694
{
695
    m_autoTestRootItem->removeChildren();
696
    m_quickTestRootItem->removeChildren();
697
    m_googleTestRootItem->removeChildren();
698 699 700
    emit testTreeModelChanged();
}

701 702 703 704 705 706 707
TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type)
{
    switch (type) {
    case AutoTest:
        return m_autoTestRootItem;
    case QuickTest:
        return m_quickTestRootItem;
708 709
    case GoogleTest:
        return m_googleTestRootItem;
710 711 712 713
    }
    QTC_ASSERT(false, return 0);
}

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

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

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

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

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

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

        // 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)
776
                toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->childItem(childRow)));
777 778
        }

779 780 781 782 783 784 785 786 787 788 789 790 791
        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);
        }
    }
}

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

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;
}
822 823 824 825 826 827

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

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

    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;
}
840 841
#endif

842 843 844 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
/***************************** 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
889
    if (leftItem->type() == TestTreeItem::Root)
890 891
        return left.row() > right.row();

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

    // 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:
903 904
        if (leftVal == rightVal)
            return left.row() > right.row();
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
        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;

931
    const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
932

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

Christian Stenger's avatar
Christian Stenger committed
943 944
} // namespace Internal
} // namespace Autotest