2014-10-07 12:30:54 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-22 10:37:55 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2014-10-07 12:30:54 +02:00
|
|
|
**
|
2016-01-22 10:37:55 +01:00
|
|
|
** This file is part of Qt Creator.
|
2014-10-07 12:30:54 +02:00
|
|
|
**
|
2016-01-22 10:37:55 +01:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
2014-10-07 12:30:54 +02:00
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-22 10:37:55 +01:00
|
|
|
** 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.
|
2014-10-07 12:30:54 +02:00
|
|
|
**
|
2016-01-22 10:37:55 +01:00
|
|
|
** 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.
|
2014-10-07 12:30:54 +02:00
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2020-10-09 13:07:55 +02:00
|
|
|
#include "testtreemodel.h"
|
|
|
|
|
|
2014-11-11 17:30:34 +01:00
|
|
|
#include "autotestconstants.h"
|
2016-02-01 15:12:10 +01:00
|
|
|
#include "autotestplugin.h"
|
2014-10-07 12:30:54 +02:00
|
|
|
#include "testcodeparser.h"
|
2016-06-01 16:22:50 +02:00
|
|
|
#include "testframeworkmanager.h"
|
2019-08-05 15:47:10 +02:00
|
|
|
#include "testprojectsettings.h"
|
2016-02-01 15:12:10 +01:00
|
|
|
#include "testsettings.h"
|
2014-10-07 12:30:54 +02:00
|
|
|
|
2014-10-21 13:10:37 +02:00
|
|
|
#include <cpptools/cppmodelmanager.h>
|
2020-10-19 14:46:21 +02:00
|
|
|
#include <projectexplorer/buildsystem.h>
|
2014-10-07 15:51:02 +02:00
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
2020-10-19 14:46:21 +02:00
|
|
|
#include <projectexplorer/target.h>
|
2015-02-12 15:48:24 +01:00
|
|
|
#include <qmljs/qmljsmodelmanagerinterface.h>
|
2014-10-21 13:10:37 +02:00
|
|
|
#include <texteditor/texteditor.h>
|
2020-10-09 13:07:55 +02:00
|
|
|
#include <utils/algorithm.h>
|
2015-04-27 16:11:28 +02:00
|
|
|
#include <utils/qtcassert.h>
|
2014-10-21 13:10:37 +02:00
|
|
|
|
2020-03-16 12:59:23 +01:00
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace Autotest::Internal;
|
|
|
|
|
|
2014-10-07 12:30:54 +02:00
|
|
|
namespace Autotest {
|
2019-08-19 10:55:32 +02:00
|
|
|
|
2020-03-26 12:01:59 +01:00
|
|
|
static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.frameworkmanager", QtWarningMsg)
|
|
|
|
|
|
2020-03-16 12:59:23 +01:00
|
|
|
static TestTreeModel *s_instance = nullptr;
|
2014-10-07 12:30:54 +02:00
|
|
|
|
2020-03-16 12:59:23 +01:00
|
|
|
TestTreeModel::TestTreeModel(TestCodeParser *parser) :
|
|
|
|
|
m_parser(parser)
|
2014-10-07 12:30:54 +02:00
|
|
|
{
|
2020-03-16 12:59:23 +01:00
|
|
|
s_instance = this;
|
|
|
|
|
|
2016-01-25 13:05:12 +01:00
|
|
|
connect(m_parser, &TestCodeParser::aboutToPerformFullParse, this,
|
2015-02-13 15:44:18 +01:00
|
|
|
&TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
|
2016-02-03 15:59:59 +01:00
|
|
|
connect(m_parser, &TestCodeParser::testParseResultReady,
|
|
|
|
|
this, &TestTreeModel::onParseResultReady, Qt::QueuedConnection);
|
2016-01-25 13:05:12 +01:00
|
|
|
connect(m_parser, &TestCodeParser::parsingFinished,
|
|
|
|
|
this, &TestTreeModel::sweep, Qt::QueuedConnection);
|
|
|
|
|
connect(m_parser, &TestCodeParser::parsingFailed,
|
|
|
|
|
this, &TestTreeModel::sweep, Qt::QueuedConnection);
|
2020-10-19 13:56:19 +02:00
|
|
|
connect(m_parser, &TestCodeParser::requestRemoveAllFrameworkItems,
|
|
|
|
|
this, &TestTreeModel::markAllFrameworkItemsForRemoval);
|
2020-03-16 12:59:23 +01:00
|
|
|
connect(m_parser, &TestCodeParser::requestRemoval,
|
|
|
|
|
this, &TestTreeModel::markForRemoval);
|
2020-09-23 10:48:29 +02:00
|
|
|
connect(this, &QAbstractItemModel::dataChanged,
|
|
|
|
|
this, &TestTreeModel::onDataChanged);
|
2016-12-12 09:35:49 +01:00
|
|
|
setupParsingConnections();
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestTreeModel *TestTreeModel::instance()
|
|
|
|
|
{
|
2017-02-13 10:05:06 +01:00
|
|
|
return s_instance;
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestTreeModel::~TestTreeModel()
|
|
|
|
|
{
|
2017-02-13 10:05:06 +01:00
|
|
|
s_instance = nullptr;
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
|
2016-02-01 15:12:10 +01:00
|
|
|
void TestTreeModel::setupParsingConnections()
|
|
|
|
|
{
|
2018-01-05 10:11:52 +01:00
|
|
|
static bool connectionsInitialized = false;
|
|
|
|
|
if (connectionsInitialized)
|
2015-02-12 15:48:24 +01:00
|
|
|
return;
|
2018-01-05 10:11:52 +01:00
|
|
|
m_parser->setDirty();
|
|
|
|
|
m_parser->setState(TestCodeParser::Idle);
|
2015-02-12 15:48:24 +01:00
|
|
|
|
2020-03-16 12:59:23 +01:00
|
|
|
SessionManager *sm = SessionManager::instance();
|
2020-10-19 14:46:21 +02:00
|
|
|
connect(sm, &SessionManager::startupProjectChanged, [this, sm](Project *project) {
|
2020-03-16 12:59:23 +01:00
|
|
|
synchronizeTestFrameworks(); // we might have project settings
|
|
|
|
|
m_parser->onStartupProjectChanged(project);
|
2020-10-19 14:46:21 +02:00
|
|
|
removeAllTestToolItems();
|
2020-07-07 10:57:38 +02:00
|
|
|
m_checkStateCache = project ? AutotestPlugin::projectSettings(project)->checkStateCache()
|
|
|
|
|
: nullptr;
|
2020-10-19 14:46:21 +02:00
|
|
|
onBuildSystemTestsUpdated(); // we may have old results if project was open before switching
|
2020-09-24 08:33:51 +02:00
|
|
|
m_failedStateCache.clear();
|
2020-10-19 14:46:21 +02:00
|
|
|
if (project) {
|
|
|
|
|
if (sm->startupBuildSystem()) {
|
|
|
|
|
connect(sm->startupBuildSystem(), &BuildSystem::testInformationUpdated,
|
|
|
|
|
this, &TestTreeModel::onBuildSystemTestsUpdated, Qt::UniqueConnection);
|
|
|
|
|
} else {
|
|
|
|
|
connect(project, &Project::activeTargetChanged,
|
|
|
|
|
this, &TestTreeModel::onTargetChanged);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-16 12:59:23 +01:00
|
|
|
});
|
2015-02-12 15:48:24 +01:00
|
|
|
|
|
|
|
|
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
|
|
|
|
|
connect(cppMM, &CppTools::CppModelManager::documentUpdated,
|
|
|
|
|
m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
|
|
|
|
|
connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles,
|
2016-01-25 13:05:12 +01:00
|
|
|
this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
|
2015-04-02 12:16:55 +02:00
|
|
|
connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated,
|
|
|
|
|
m_parser, &TestCodeParser::onProjectPartsUpdated);
|
2015-02-12 15:48:24 +01:00
|
|
|
|
|
|
|
|
QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance();
|
|
|
|
|
connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
|
|
|
|
|
m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
|
|
|
|
|
connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
|
2016-01-25 13:05:12 +01:00
|
|
|
this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
|
2018-01-05 10:11:52 +01:00
|
|
|
connectionsInitialized = true;
|
2015-02-12 15:48:24 +01:00
|
|
|
}
|
|
|
|
|
|
2014-10-07 12:30:54 +02:00
|
|
|
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
ITestTreeItem *item = static_cast<ITestTreeItem *>(index.internalPointer());
|
2015-09-10 10:57:15 +02:00
|
|
|
if (item && item->setData(index.column(), value, role)) {
|
2020-09-23 10:48:29 +02:00
|
|
|
emit dataChanged(index, index, {role});
|
2015-09-10 10:57:15 +02:00
|
|
|
if (role == Qt::CheckStateRole) {
|
2018-01-23 16:12:47 +01:00
|
|
|
Qt::CheckState checked = item->checked();
|
|
|
|
|
if (item->hasChildren() && checked != Qt::PartiallyChecked) {
|
|
|
|
|
// handle the new checkstate for children as well...
|
|
|
|
|
for (Utils::TreeItem *child : *item) {
|
|
|
|
|
const QModelIndex &idx = indexForItem(child);
|
|
|
|
|
setData(idx, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
|
|
|
|
|
}
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
2020-10-12 14:11:10 +02:00
|
|
|
if (item->parent() != rootItem()) {
|
|
|
|
|
auto parent = static_cast<ITestTreeItem *>(item->parent());
|
|
|
|
|
if (parent->checked() != checked)
|
|
|
|
|
revalidateCheckState(parent); // handle parent too
|
|
|
|
|
}
|
2018-01-23 16:12:47 +01:00
|
|
|
return true;
|
2020-10-19 13:56:19 +02:00
|
|
|
} else if (role == FailedRole) {
|
2020-11-25 08:18:55 +01:00
|
|
|
if (item->testBase()->type() == ITestBase::Framework)
|
2020-10-19 13:56:19 +02:00
|
|
|
m_failedStateCache.insert(static_cast<TestTreeItem *>(item), true);
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid())
|
|
|
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
ITestTreeItem *item = static_cast<ITestTreeItem *>(itemForIndex(index));
|
2016-04-28 16:42:14 +02:00
|
|
|
return item->flags(index.column());
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TestTreeModel::hasTests() const
|
|
|
|
|
{
|
2017-02-07 08:53:00 +01:00
|
|
|
for (Utils::TreeItem *frameworkRoot : *rootItem()) {
|
2016-06-01 16:22:50 +02:00
|
|
|
if (frameworkRoot->hasChildren())
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2014-10-07 12:30:54 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> TestTreeModel::getAllTestCases() const
|
2014-10-07 15:51:02 +02:00
|
|
|
{
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> result;
|
2020-10-29 14:27:55 +01:00
|
|
|
forItemsAtLevel<1>([&result](ITestTreeItem *testRoot) {
|
|
|
|
|
result.append(testRoot->getAllTestConfigurations());
|
|
|
|
|
});
|
2014-10-07 15:51:02 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> TestTreeModel::getSelectedTests() const
|
2014-10-07 15:51:02 +02:00
|
|
|
{
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> result;
|
2020-10-29 14:27:55 +01:00
|
|
|
forItemsAtLevel<1>([&result](ITestTreeItem *testRoot) {
|
|
|
|
|
result.append(testRoot->getSelectedTestConfigurations());
|
|
|
|
|
});
|
2014-10-07 15:51:02 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> TestTreeModel::getFailedTests() const
|
2020-09-11 14:30:15 +02:00
|
|
|
{
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> result;
|
2020-10-29 14:27:55 +01:00
|
|
|
forItemsAtLevel<1>([&result](ITestTreeItem *testRoot) {
|
|
|
|
|
result.append(testRoot->getFailedTestConfigurations());
|
|
|
|
|
});
|
2020-09-11 14:30:15 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> TestTreeModel::getTestsForFile(const Utils::FilePath &fileName) const
|
2018-05-05 22:28:55 +03:00
|
|
|
{
|
2020-10-13 11:37:37 +02:00
|
|
|
QList<ITestConfiguration *> result;
|
2020-10-29 14:27:55 +01:00
|
|
|
forItemsAtLevel<1>([&result, &fileName](ITestTreeItem *testRoot) {
|
|
|
|
|
if (testRoot->testBase()->type() == ITestBase::Framework)
|
|
|
|
|
result.append(static_cast<TestTreeItem *>(testRoot)->getTestConfigurationsForFile(fileName));
|
|
|
|
|
});
|
2018-05-05 22:28:55 +03:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 17:57:37 +01:00
|
|
|
QList<TestTreeItem *> TestTreeModel::testItemsByName(TestTreeItem *root, const QString &testName)
|
|
|
|
|
{
|
|
|
|
|
QList<TestTreeItem *> result;
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
root->forFirstLevelChildItems([&testName, &result, this](TestTreeItem *node) {
|
2019-05-14 11:26:16 +02:00
|
|
|
if (node->type() == TestTreeItem::TestSuite || node->type() == TestTreeItem::TestCase) {
|
2018-02-16 17:57:37 +01:00
|
|
|
if (node->name() == testName) {
|
|
|
|
|
result << node;
|
2019-05-14 11:26:16 +02:00
|
|
|
return; // prioritize test suites and cases over test functions
|
2018-02-16 17:57:37 +01:00
|
|
|
}
|
2020-10-12 14:11:10 +02:00
|
|
|
TestTreeItem *testCase = node->findFirstLevelChildItem([&testName](TestTreeItem *it) {
|
2018-02-16 17:57:37 +01:00
|
|
|
QTC_ASSERT(it, return false);
|
2019-10-10 11:23:27 +02:00
|
|
|
return (it->type() == TestTreeItem::TestCase
|
|
|
|
|
|| it->type() == TestTreeItem::TestFunction) && it->name() == testName;
|
2018-04-17 15:53:06 +02:00
|
|
|
}); // collect only actual tests, not special functions like init, cleanup etc.
|
|
|
|
|
if (testCase)
|
|
|
|
|
result << testCase;
|
2018-02-16 17:57:37 +01:00
|
|
|
} else {
|
|
|
|
|
result << testItemsByName(node, testName);
|
|
|
|
|
}
|
2018-04-17 15:53:06 +02:00
|
|
|
});
|
2018-02-16 17:57:37 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 14:46:21 +02:00
|
|
|
void TestTreeModel::onTargetChanged(Target *target)
|
|
|
|
|
{
|
|
|
|
|
if (target && target->buildSystem()) {
|
|
|
|
|
const Target *topLevelTarget = SessionManager::startupProject()->targets().first();
|
|
|
|
|
connect(topLevelTarget->buildSystem(), &BuildSystem::testInformationUpdated,
|
|
|
|
|
this, &TestTreeModel::onBuildSystemTestsUpdated, Qt::UniqueConnection);
|
|
|
|
|
disconnect(target->project(), &Project::activeTargetChanged,
|
|
|
|
|
this, &TestTreeModel::onTargetChanged);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::onBuildSystemTestsUpdated()
|
|
|
|
|
{
|
|
|
|
|
const BuildSystem *bs = SessionManager::startupBuildSystem();
|
|
|
|
|
if (!bs || !bs->project())
|
|
|
|
|
return;
|
|
|
|
|
|
2021-01-19 07:50:29 +01:00
|
|
|
QTC_ASSERT(m_checkStateCache, return);
|
2020-10-19 14:46:21 +02:00
|
|
|
m_checkStateCache->evolve(ITestBase::Tool);
|
|
|
|
|
|
|
|
|
|
ITestTool *testTool = TestFrameworkManager::testToolForBuildSystemId(bs->project()->id());
|
|
|
|
|
if (!testTool || !testTool->active())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ITestTreeItem *rootNode = testTool->rootNode();
|
|
|
|
|
QTC_ASSERT(rootNode, return);
|
|
|
|
|
rootNode->removeChildren();
|
|
|
|
|
for (const auto &tci : bs->testcasesInfo()) {
|
|
|
|
|
ITestTreeItem *item = testTool->createItemFromTestCaseInfo(tci);
|
|
|
|
|
QTC_ASSERT(item, continue);
|
|
|
|
|
if (Utils::optional<Qt::CheckState> cached = m_checkStateCache->get(item))
|
|
|
|
|
item->setData(0, cached.value(), Qt::CheckStateRole);
|
|
|
|
|
m_checkStateCache->insert(item, item->checked());
|
|
|
|
|
rootNode->appendChild(item);
|
|
|
|
|
}
|
|
|
|
|
revalidateCheckState(rootNode);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 14:27:55 +01:00
|
|
|
const QList<TestTreeItem *> TestTreeModel::frameworkRootNodes() const
|
|
|
|
|
{
|
|
|
|
|
QList<TestTreeItem *> result;
|
|
|
|
|
forItemsAtLevel<1>([&result](ITestTreeItem *rootNode) {
|
|
|
|
|
if (auto framework = rootNode->testBase()->asFramework())
|
|
|
|
|
result.append(framework->rootNode());
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QList<ITestTreeItem *> TestTreeModel::testToolRootNodes() const
|
|
|
|
|
{
|
|
|
|
|
QList<ITestTreeItem *> result;
|
|
|
|
|
forItemsAtLevel<1>([&result](ITestTreeItem *rootNode) {
|
|
|
|
|
if (auto testTool = rootNode->testBase()->asTestTool())
|
|
|
|
|
result.append(testTool->rootNode());
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 17:57:37 +01:00
|
|
|
QList<TestTreeItem *> TestTreeModel::testItemsByName(const QString &testName)
|
|
|
|
|
{
|
|
|
|
|
QList<TestTreeItem *> result;
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *frameworkRoot : frameworkRootNodes())
|
|
|
|
|
result << testItemsByName(frameworkRoot, testName);
|
2018-02-16 17:57:37 +01:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 10:42:01 +02:00
|
|
|
void TestTreeModel::synchronizeTestFrameworks()
|
2015-02-05 16:07:45 +01:00
|
|
|
{
|
2019-08-05 15:47:10 +02:00
|
|
|
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
|
2020-03-13 13:54:33 +01:00
|
|
|
TestFrameworks sorted;
|
2021-01-14 14:42:49 +01:00
|
|
|
if (!project || AutotestPlugin::projectSettings(project)->useGlobalSettings()) {
|
2020-03-26 12:01:59 +01:00
|
|
|
sorted = Utils::filtered(TestFrameworkManager::registeredFrameworks(),
|
|
|
|
|
&ITestFramework::active);
|
|
|
|
|
qCDebug(LOG) << "Active frameworks sorted by priority" << sorted;
|
2019-08-05 15:47:10 +02:00
|
|
|
} else { // we've got custom project settings
|
|
|
|
|
const TestProjectSettings *settings = AutotestPlugin::projectSettings(project);
|
2020-11-17 00:23:17 +01:00
|
|
|
const QHash<ITestFramework *, bool> active = settings->activeFrameworks();
|
2021-01-01 19:19:15 +01:00
|
|
|
sorted = Utils::filtered(TestFrameworkManager::registeredFrameworks(),
|
|
|
|
|
[active](ITestFramework *framework) {
|
|
|
|
|
return active.value(framework, false);
|
2019-08-05 15:47:10 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pre-check to avoid further processing when frameworks are unchanged
|
|
|
|
|
Utils::TreeItem *invisibleRoot = rootItem();
|
2020-03-13 13:54:33 +01:00
|
|
|
QSet<ITestFramework *> newlyAdded;
|
2020-10-19 14:46:21 +02:00
|
|
|
QList<ITestTreeItem *> oldFrameworkRoots;
|
2019-09-12 14:14:54 +02:00
|
|
|
for (Utils::TreeItem *oldFrameworkRoot : *invisibleRoot)
|
2020-10-19 14:46:21 +02:00
|
|
|
oldFrameworkRoots.append(static_cast<ITestTreeItem *>(oldFrameworkRoot));
|
2019-09-12 14:14:54 +02:00
|
|
|
|
2020-10-19 14:46:21 +02:00
|
|
|
for (ITestTreeItem *oldFrameworkRoot : oldFrameworkRoots)
|
2019-09-12 14:14:54 +02:00
|
|
|
takeItem(oldFrameworkRoot); // do NOT delete the ptr is still held by TestFrameworkManager
|
|
|
|
|
|
2020-10-19 14:46:21 +02:00
|
|
|
for (ITestFramework *framework : qAsConst(sorted)) {
|
2020-03-13 13:54:33 +01:00
|
|
|
TestTreeItem *frameworkRootNode = framework->rootNode();
|
2019-09-12 14:14:54 +02:00
|
|
|
invisibleRoot->appendChild(frameworkRootNode);
|
|
|
|
|
if (!oldFrameworkRoots.removeOne(frameworkRootNode))
|
2020-03-13 13:54:33 +01:00
|
|
|
newlyAdded.insert(framework);
|
2019-08-05 15:47:10 +02:00
|
|
|
}
|
2020-10-19 14:46:21 +02:00
|
|
|
for (ITestTreeItem *oldFrameworkRoot : oldFrameworkRoots) {
|
2020-11-25 08:18:55 +01:00
|
|
|
if (oldFrameworkRoot->testBase()->type() == ITestBase::Framework)
|
2020-10-19 14:46:21 +02:00
|
|
|
oldFrameworkRoot->removeChildren();
|
|
|
|
|
else // re-add the test tools - they are handled separately
|
|
|
|
|
invisibleRoot->appendChild(oldFrameworkRoot);
|
|
|
|
|
}
|
2016-06-01 16:22:50 +02:00
|
|
|
|
2020-03-13 13:54:33 +01:00
|
|
|
m_parser->syncTestFrameworks(sorted);
|
2019-09-12 14:14:54 +02:00
|
|
|
if (!newlyAdded.isEmpty())
|
|
|
|
|
m_parser->updateTestTree(newlyAdded);
|
2020-10-19 14:46:21 +02:00
|
|
|
emit updatedActiveFrameworks(invisibleRoot->childCount());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::synchronizeTestTools()
|
|
|
|
|
{
|
2021-01-08 15:53:30 +01:00
|
|
|
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
|
|
|
|
|
TestTools tools;
|
|
|
|
|
if (!project || AutotestPlugin::projectSettings(project)->useGlobalSettings()) {
|
|
|
|
|
tools = Utils::filtered(TestFrameworkManager::registeredTestTools(),
|
|
|
|
|
&ITestFramework::active);
|
|
|
|
|
qCDebug(LOG) << "Active test tools" << tools; // FIXME tools aren't sorted
|
|
|
|
|
} else { // we've got custom project settings
|
|
|
|
|
const TestProjectSettings *settings = AutotestPlugin::projectSettings(project);
|
|
|
|
|
const QHash<ITestTool *, bool> active = settings->activeTestTools();
|
|
|
|
|
tools = Utils::filtered(TestFrameworkManager::registeredTestTools(),
|
|
|
|
|
[active](ITestTool *testTool) {
|
|
|
|
|
return active.value(testTool, false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 14:46:21 +02:00
|
|
|
// pre-check to avoid further processing when test tools are unchanged
|
|
|
|
|
Utils::TreeItem *invisibleRoot = rootItem();
|
|
|
|
|
QSet<ITestTool *> newlyAdded;
|
|
|
|
|
QList<ITestTreeItem *> oldFrameworkRoots;
|
|
|
|
|
for (Utils::TreeItem *oldFrameworkRoot : *invisibleRoot) {
|
2021-01-29 19:56:53 +01:00
|
|
|
auto item = static_cast<ITestTreeItem *>(oldFrameworkRoot);
|
2020-10-19 14:46:21 +02:00
|
|
|
if (item->testBase()->asTestTool())
|
|
|
|
|
oldFrameworkRoots.append(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (ITestTreeItem *oldFrameworkRoot : oldFrameworkRoots)
|
|
|
|
|
takeItem(oldFrameworkRoot); // do NOT delete the ptr is still held by TestFrameworkManager
|
|
|
|
|
|
2021-01-08 15:53:30 +01:00
|
|
|
for (ITestTool *testTool : tools) {
|
2020-10-19 14:46:21 +02:00
|
|
|
ITestTreeItem *testToolRootNode = testTool->rootNode();
|
|
|
|
|
if (testTool->active()) {
|
|
|
|
|
invisibleRoot->appendChild(testToolRootNode);
|
|
|
|
|
if (!oldFrameworkRoots.removeOne(testToolRootNode))
|
|
|
|
|
newlyAdded.insert(testTool);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (project) {
|
|
|
|
|
const QList<Target *> &allTargets = project->targets();
|
|
|
|
|
auto target = allTargets.empty() ? nullptr : allTargets.first();
|
|
|
|
|
if (QTC_GUARD(target)) {
|
|
|
|
|
auto bs = target->buildSystem();
|
|
|
|
|
for (ITestTool *testTool : newlyAdded) {
|
|
|
|
|
ITestTreeItem *rootNode = testTool->rootNode();
|
|
|
|
|
QTC_ASSERT(rootNode, return);
|
|
|
|
|
rootNode->removeChildren();
|
|
|
|
|
for (const auto &tci : bs->testcasesInfo()) {
|
|
|
|
|
ITestTreeItem *item = testTool->createItemFromTestCaseInfo(tci);
|
|
|
|
|
QTC_ASSERT(item, continue);
|
|
|
|
|
if (Utils::optional<Qt::CheckState> cached = m_checkStateCache->get(item))
|
|
|
|
|
item->setData(0, cached.value(), Qt::CheckStateRole);
|
|
|
|
|
m_checkStateCache->insert(item, item->checked());
|
|
|
|
|
rootNode->appendChild(item);
|
|
|
|
|
}
|
|
|
|
|
revalidateCheckState(rootNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
emit updatedActiveFrameworks(invisibleRoot->childCount());
|
2014-11-06 16:01:06 +01:00
|
|
|
}
|
|
|
|
|
|
2018-02-28 09:42:13 +01:00
|
|
|
void TestTreeModel::filterAndInsert(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled)
|
|
|
|
|
{
|
|
|
|
|
TestTreeItem *filtered = item->applyFilters();
|
2019-05-15 07:20:18 +02:00
|
|
|
if (item->shouldBeAddedAfterFiltering())
|
2018-02-28 09:42:13 +01:00
|
|
|
insertItemInParent(item, root, groupingEnabled);
|
|
|
|
|
else // might be that all children have been filtered out
|
|
|
|
|
delete item;
|
|
|
|
|
if (filtered)
|
|
|
|
|
insertItemInParent(filtered, root, groupingEnabled);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-26 13:59:38 +02:00
|
|
|
void TestTreeModel::rebuild(const QList<Utils::Id> &frameworkIds)
|
2017-12-06 08:45:36 +01:00
|
|
|
{
|
2020-06-26 13:59:38 +02:00
|
|
|
for (const Utils::Id &id : frameworkIds) {
|
2020-03-13 13:54:33 +01:00
|
|
|
ITestFramework *framework = TestFrameworkManager::frameworkForId(id);
|
|
|
|
|
TestTreeItem *frameworkRoot = framework->rootNode();
|
|
|
|
|
const bool groupingEnabled = framework->grouping();
|
2017-12-06 08:45:36 +01:00
|
|
|
for (int row = frameworkRoot->childCount() - 1; row >= 0; --row) {
|
2020-10-12 14:11:10 +02:00
|
|
|
auto testItem = frameworkRoot->childItem(row);
|
2018-02-28 09:42:13 +01:00
|
|
|
if (testItem->type() == TestTreeItem::GroupNode) {
|
|
|
|
|
// process children of group node and delete it afterwards if necessary
|
2017-12-06 08:45:36 +01:00
|
|
|
for (int childRow = testItem->childCount() - 1; childRow >= 0; --childRow) {
|
|
|
|
|
// FIXME should this be done recursively until we have a non-GroupNode?
|
2020-10-12 14:11:10 +02:00
|
|
|
TestTreeItem *childTestItem = testItem->childItem(childRow);
|
2017-12-06 08:45:36 +01:00
|
|
|
takeItem(childTestItem);
|
2018-02-28 09:42:13 +01:00
|
|
|
filterAndInsert(childTestItem, frameworkRoot, groupingEnabled);
|
2017-12-06 08:45:36 +01:00
|
|
|
}
|
2018-02-28 09:42:13 +01:00
|
|
|
if (!groupingEnabled || testItem->childCount() == 0)
|
|
|
|
|
delete takeItem(testItem);
|
2017-12-06 08:45:36 +01:00
|
|
|
} else {
|
|
|
|
|
takeItem(testItem);
|
2018-02-28 09:42:13 +01:00
|
|
|
filterAndInsert(testItem, frameworkRoot, groupingEnabled);
|
2017-12-06 08:45:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
2018-04-12 15:13:07 +02:00
|
|
|
revalidateCheckState(frameworkRoot);
|
2017-12-06 08:45:36 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 14:27:01 +02:00
|
|
|
void TestTreeModel::updateCheckStateCache()
|
|
|
|
|
{
|
2020-10-19 14:46:21 +02:00
|
|
|
m_checkStateCache->evolve(ITestBase::Framework);
|
2020-06-03 14:27:01 +02:00
|
|
|
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *rootNode : frameworkRootNodes()) {
|
|
|
|
|
rootNode->forAllChildItems([this](TestTreeItem *childItem) {
|
2020-07-07 10:57:38 +02:00
|
|
|
m_checkStateCache->insert(childItem, childItem->checked());
|
2020-06-03 14:27:01 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-11 14:30:15 +02:00
|
|
|
bool TestTreeModel::hasFailedTests() const
|
|
|
|
|
{
|
|
|
|
|
auto failedItem = rootItem()->findAnyChild([](Utils::TreeItem *it) {
|
|
|
|
|
return it->data(0, FailedRole).toBool();
|
|
|
|
|
});
|
|
|
|
|
return failedItem != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::clearFailedMarks()
|
|
|
|
|
{
|
|
|
|
|
for (Utils::TreeItem *rootNode : *rootItem()) {
|
|
|
|
|
rootNode->forAllChildren([](Utils::TreeItem *child) {
|
|
|
|
|
child->setData(0, false, FailedRole);
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-09-24 08:33:51 +02:00
|
|
|
m_failedStateCache.clear();
|
2020-09-11 14:30:15 +02:00
|
|
|
}
|
|
|
|
|
|
2016-01-25 13:05:12 +01:00
|
|
|
void TestTreeModel::removeFiles(const QStringList &files)
|
2014-10-07 12:30:54 +02:00
|
|
|
{
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const QString &file : files)
|
2016-01-25 13:05:12 +01:00
|
|
|
markForRemoval(file);
|
|
|
|
|
sweep();
|
2015-12-07 15:18:25 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-19 13:56:19 +02:00
|
|
|
void TestTreeModel::markAllFrameworkItemsForRemoval()
|
2016-01-26 13:52:45 +01:00
|
|
|
{
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *frameworkRoot : frameworkRootNodes()) {
|
|
|
|
|
frameworkRoot->forFirstLevelChildItems([](TestTreeItem *child) {
|
|
|
|
|
child->markForRemovalRecursively(true);
|
|
|
|
|
});
|
2016-06-06 14:18:38 +02:00
|
|
|
}
|
2016-01-26 13:52:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::markForRemoval(const QString &filePath)
|
|
|
|
|
{
|
|
|
|
|
if (filePath.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *frameworkRoot : frameworkRootNodes()) {
|
|
|
|
|
for (int childRow = frameworkRoot->childCount() - 1; childRow >= 0; --childRow) {
|
|
|
|
|
TestTreeItem *child = frameworkRoot->childItem(childRow);
|
2016-04-28 16:42:14 +02:00
|
|
|
child->markForRemovalRecursively(filePath);
|
2016-01-26 13:52:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::sweep()
|
|
|
|
|
{
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *frameworkRoot : frameworkRootNodes()) {
|
|
|
|
|
sweepChildren(frameworkRoot);
|
|
|
|
|
revalidateCheckState(frameworkRoot);
|
2016-01-26 13:52:45 +01:00
|
|
|
}
|
2016-02-29 11:14:40 +01:00
|
|
|
// even if nothing has changed by the sweeping we might had parse which added or modified items
|
|
|
|
|
emit testTreeModelChanged();
|
2016-02-10 10:43:31 +01:00
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
if (m_parser->state() == TestCodeParser::Idle && !m_parser->furtherParsingExpected())
|
|
|
|
|
emit sweepingDone();
|
|
|
|
|
#endif
|
2016-01-26 13:52:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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) {
|
2020-10-12 14:11:10 +02:00
|
|
|
TestTreeItem *child = item->childItem(row);
|
2016-01-26 13:52:45 +01:00
|
|
|
|
2017-01-17 10:21:39 +01:00
|
|
|
if (child->type() != TestTreeItem::Root && child->markedForRemoval()) {
|
2016-07-04 15:53:53 +02:00
|
|
|
destroyItem(child);
|
2018-01-23 16:12:47 +01:00
|
|
|
revalidateCheckState(item);
|
2016-01-26 13:52:45 +01:00
|
|
|
hasChanged = true;
|
2017-01-09 07:53:55 +01:00
|
|
|
} else if (child->hasChildren()) {
|
2016-01-26 13:52:45 +01:00
|
|
|
hasChanged |= sweepChildren(child);
|
2018-01-18 09:19:56 +01:00
|
|
|
if (!child->hasChildren() && child->removeOnSweepIfEmpty()) {
|
|
|
|
|
destroyItem(child);
|
|
|
|
|
revalidateCheckState(item);
|
|
|
|
|
}
|
2017-01-09 07:53:55 +01:00
|
|
|
} else {
|
|
|
|
|
hasChanged |= child->newlyAdded();
|
2016-01-26 13:52:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return hasChanged;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-13 13:38:43 +01:00
|
|
|
static TestTreeItem *fullCopyOf(TestTreeItem *other)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(other, return nullptr);
|
|
|
|
|
TestTreeItem *result = other->copyWithoutChildren();
|
|
|
|
|
for (int row = 0, count = other->childCount(); row < count; ++row)
|
2020-10-12 14:11:10 +02:00
|
|
|
result->appendChild(fullCopyOf(other->childItem(row)));
|
2018-03-13 13:38:43 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
static void applyParentCheckState(ITestTreeItem *parent, ITestTreeItem *newItem)
|
2018-04-12 15:13:07 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(parent && newItem, return);
|
|
|
|
|
|
|
|
|
|
if (parent->checked() != newItem->checked()) {
|
|
|
|
|
const Qt::CheckState checkState = parent->checked() == Qt::Unchecked ? Qt::Unchecked
|
|
|
|
|
: Qt::Checked;
|
|
|
|
|
newItem->setData(0, checkState, Qt::CheckStateRole);
|
|
|
|
|
newItem->forAllChildren([checkState](Utils::TreeItem *it) {
|
|
|
|
|
it->setData(0, checkState, Qt::CheckStateRole);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-06 08:45:36 +01:00
|
|
|
void TestTreeModel::insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled)
|
|
|
|
|
{
|
|
|
|
|
TestTreeItem *parentNode = root;
|
2018-08-06 13:57:17 +02:00
|
|
|
if (groupingEnabled && item->isGroupable()) {
|
2020-10-12 14:11:10 +02:00
|
|
|
parentNode = root->findFirstLevelChildItem([item] (const TestTreeItem *it) {
|
2017-12-06 08:45:36 +01:00
|
|
|
return it->isGroupNodeFor(item);
|
|
|
|
|
});
|
|
|
|
|
if (!parentNode) {
|
|
|
|
|
parentNode = item->createParentGroupNode();
|
2018-08-06 13:57:17 +02:00
|
|
|
if (!QTC_GUARD(parentNode)) // we might not get a group node at all
|
2017-12-06 08:45:36 +01:00
|
|
|
parentNode = root;
|
|
|
|
|
else
|
|
|
|
|
root->appendChild(parentNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-13 13:38:43 +01:00
|
|
|
// check if a similar item is already present (can happen for rebuild())
|
2018-04-12 15:13:07 +02:00
|
|
|
if (auto otherItem = parentNode->findChild(item)) {
|
2018-03-13 13:38:43 +01:00
|
|
|
// only handle item's children and add them to the already present one
|
2018-04-12 15:13:07 +02:00
|
|
|
for (int row = 0, count = item->childCount(); row < count; ++row) {
|
2020-10-12 14:11:10 +02:00
|
|
|
TestTreeItem *child = fullCopyOf(item->childItem(row));
|
2020-06-03 14:27:01 +02:00
|
|
|
// use check state of the original
|
|
|
|
|
child->setData(0, item->childAt(row)->checked(), Qt::CheckStateRole);
|
2018-04-12 15:13:07 +02:00
|
|
|
otherItem->appendChild(child);
|
2020-06-03 14:27:01 +02:00
|
|
|
revalidateCheckState(child);
|
2018-04-12 15:13:07 +02:00
|
|
|
}
|
2018-03-13 13:38:43 +01:00
|
|
|
delete item;
|
|
|
|
|
} else {
|
2020-06-03 14:27:01 +02:00
|
|
|
// restore former check state if available
|
2020-07-07 10:57:38 +02:00
|
|
|
Utils::optional<Qt::CheckState> cached = m_checkStateCache->get(item);
|
2020-06-18 12:32:48 +02:00
|
|
|
if (cached.has_value())
|
2020-06-03 14:27:01 +02:00
|
|
|
item->setData(0, cached.value(), Qt::CheckStateRole);
|
|
|
|
|
else
|
|
|
|
|
applyParentCheckState(parentNode, item);
|
2020-09-24 08:33:51 +02:00
|
|
|
// ..and the failed state if available
|
|
|
|
|
Utils::optional<bool> failed = m_failedStateCache.get(item);
|
|
|
|
|
if (failed.has_value())
|
|
|
|
|
item->setData(0, *failed, FailedRole);
|
2018-03-13 13:38:43 +01:00
|
|
|
parentNode->appendChild(item);
|
2020-06-03 14:27:01 +02:00
|
|
|
revalidateCheckState(parentNode);
|
2018-03-13 13:38:43 +01:00
|
|
|
}
|
2018-01-23 16:12:47 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
static Qt::CheckState computeCheckStateByChildren(ITestTreeItem *item)
|
2018-01-23 16:12:47 +01:00
|
|
|
{
|
|
|
|
|
Qt::CheckState newState = Qt::Checked;
|
|
|
|
|
bool foundChecked = false;
|
|
|
|
|
bool foundUnchecked = false;
|
|
|
|
|
bool foundPartiallyChecked = false;
|
2020-10-12 14:11:10 +02:00
|
|
|
|
|
|
|
|
item->forFirstLevelChildren([&](ITestTreeItem *child) {
|
|
|
|
|
if (foundPartiallyChecked || (foundChecked && foundUnchecked)) {
|
|
|
|
|
newState = Qt::PartiallyChecked;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-01-23 16:12:47 +01:00
|
|
|
switch (child->type()) {
|
|
|
|
|
case TestTreeItem::TestDataFunction:
|
|
|
|
|
case TestTreeItem::TestSpecialFunction:
|
2020-10-12 14:11:10 +02:00
|
|
|
return;
|
2018-01-23 16:12:47 +01:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foundChecked |= (child->checked() == Qt::Checked);
|
|
|
|
|
foundUnchecked |= (child->checked() == Qt::Unchecked);
|
|
|
|
|
foundPartiallyChecked |= (child->checked() == Qt::PartiallyChecked);
|
2020-10-12 14:11:10 +02:00
|
|
|
});
|
|
|
|
|
|
2018-01-23 16:12:47 +01:00
|
|
|
if (newState != Qt::PartiallyChecked)
|
|
|
|
|
newState = foundUnchecked ? Qt::Unchecked : Qt::Checked;
|
2020-10-12 14:11:10 +02:00
|
|
|
return newState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::revalidateCheckState(ITestTreeItem *item)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(item, return);
|
|
|
|
|
|
|
|
|
|
const ITestTreeItem::Type type = item->type();
|
|
|
|
|
if (type == ITestTreeItem::TestSpecialFunction || type == ITestTreeItem::TestDataFunction
|
|
|
|
|
|| type == ITestTreeItem::TestDataTag) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const Qt::CheckState oldState = Qt::CheckState(item->data(0, Qt::CheckStateRole).toInt());
|
|
|
|
|
Qt::CheckState newState = computeCheckStateByChildren(item);
|
2018-01-23 16:12:47 +01:00
|
|
|
if (oldState != newState) {
|
|
|
|
|
item->setData(0, newState, Qt::CheckStateRole);
|
2020-09-23 10:48:29 +02:00
|
|
|
emit dataChanged(item->index(), item->index(), {Qt::CheckStateRole});
|
2020-10-12 14:11:10 +02:00
|
|
|
if (item->parent() != rootItem()) {
|
|
|
|
|
auto parent = static_cast<ITestTreeItem *>(item->parent());
|
|
|
|
|
if (parent->checked() != newState)
|
|
|
|
|
revalidateCheckState(parent);
|
|
|
|
|
}
|
2018-01-23 16:12:47 +01:00
|
|
|
}
|
2017-12-06 08:45:36 +01:00
|
|
|
}
|
|
|
|
|
|
2016-04-12 12:44:56 +02:00
|
|
|
void TestTreeModel::onParseResultReady(const TestParseResultPtr result)
|
2016-02-03 15:59:59 +01:00
|
|
|
{
|
2020-10-19 13:56:19 +02:00
|
|
|
ITestFramework *framework = result->base->asFramework();
|
|
|
|
|
QTC_ASSERT(framework, return);
|
|
|
|
|
TestTreeItem *rootNode = framework->rootNode();
|
2016-05-09 10:27:47 +02:00
|
|
|
QTC_ASSERT(rootNode, return);
|
|
|
|
|
handleParseResult(result.data(), rootNode);
|
2016-02-03 15:59:59 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-23 10:48:29 +02:00
|
|
|
void Autotest::TestTreeModel::onDataChanged(const QModelIndex &topLeft,
|
|
|
|
|
const QModelIndex &bottomRight,
|
|
|
|
|
const QVector<int> &roles)
|
|
|
|
|
{
|
|
|
|
|
const QModelIndex parent = topLeft.parent();
|
|
|
|
|
QTC_ASSERT(parent == bottomRight.parent(), return);
|
|
|
|
|
if (!roles.isEmpty() && !roles.contains(Qt::CheckStateRole))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (int row = topLeft.row(), endRow = bottomRight.row(); row <= endRow; ++row) {
|
2020-10-12 14:11:10 +02:00
|
|
|
if (auto item = static_cast<ITestTreeItem *>(itemForIndex(index(row, 0, parent))))
|
2020-09-23 10:48:29 +02:00
|
|
|
m_checkStateCache->insert(item, item->checked());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-09 10:27:47 +02:00
|
|
|
void TestTreeModel::handleParseResult(const TestParseResult *result, TestTreeItem *parentNode)
|
2016-02-08 15:56:07 +01:00
|
|
|
{
|
2020-10-19 13:56:19 +02:00
|
|
|
const bool groupingEnabled = result->base->asFramework()->grouping();
|
2016-05-09 10:27:47 +02:00
|
|
|
// lookup existing items
|
|
|
|
|
if (TestTreeItem *toBeModified = parentNode->find(result)) {
|
|
|
|
|
// found existing item... Do not remove
|
|
|
|
|
toBeModified->markForRemoval(false);
|
2017-12-06 08:45:36 +01:00
|
|
|
// if it's a reparse we need to mark the group node as well to avoid purging it in sweep()
|
|
|
|
|
if (groupingEnabled) {
|
|
|
|
|
if (auto directParent = toBeModified->parentItem()) {
|
|
|
|
|
if (directParent->type() == TestTreeItem::GroupNode)
|
|
|
|
|
directParent->markForRemoval(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-09 10:27:47 +02:00
|
|
|
// modify and when content has changed inform ui
|
|
|
|
|
if (toBeModified->modify(result)) {
|
|
|
|
|
const QModelIndex &idx = indexForItem(toBeModified);
|
|
|
|
|
emit dataChanged(idx, idx);
|
2016-02-08 15:56:07 +01:00
|
|
|
}
|
2016-05-09 10:27:47 +02:00
|
|
|
// recursively handle children of this item
|
2017-02-13 10:05:06 +01:00
|
|
|
for (const TestParseResult *child : result->children)
|
2016-05-09 10:27:47 +02:00
|
|
|
handleParseResult(child, toBeModified);
|
2016-04-11 14:50:04 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// if there's no matching item, add the new one
|
2016-05-09 10:27:47 +02:00
|
|
|
TestTreeItem *newItem = result->createTestTreeItem();
|
|
|
|
|
QTC_ASSERT(newItem, return);
|
2017-12-06 08:45:36 +01:00
|
|
|
|
2020-09-24 08:33:51 +02:00
|
|
|
// restore former check state and fail state if available
|
2020-10-12 14:11:10 +02:00
|
|
|
newItem->forAllChildItems([this](TestTreeItem *childItem) {
|
2020-07-07 10:57:38 +02:00
|
|
|
Utils::optional<Qt::CheckState> cached = m_checkStateCache->get(childItem);
|
2020-06-18 12:32:48 +02:00
|
|
|
if (cached.has_value())
|
2020-06-03 14:27:01 +02:00
|
|
|
childItem->setData(0, cached.value(), Qt::CheckStateRole);
|
2020-09-24 08:33:51 +02:00
|
|
|
Utils::optional<bool> failed = m_failedStateCache.get(childItem);
|
|
|
|
|
if (failed.has_value())
|
|
|
|
|
childItem->setData(0, *failed, FailedRole);
|
2020-06-03 14:27:01 +02:00
|
|
|
});
|
2018-02-28 09:42:13 +01:00
|
|
|
// it might be necessary to "split" created item
|
|
|
|
|
filterAndInsert(newItem, parentNode, groupingEnabled);
|
2016-02-08 15:56:07 +01:00
|
|
|
}
|
|
|
|
|
|
2015-02-05 16:07:45 +01:00
|
|
|
void TestTreeModel::removeAllTestItems()
|
2014-11-06 16:01:06 +01:00
|
|
|
{
|
2020-10-29 14:27:55 +01:00
|
|
|
for (TestTreeItem *item : frameworkRootNodes()) {
|
2020-10-19 14:46:21 +02:00
|
|
|
item->removeChildren();
|
|
|
|
|
if (item->checked() == Qt::PartiallyChecked)
|
|
|
|
|
item->setData(0, Qt::Checked, Qt::CheckStateRole);
|
|
|
|
|
}
|
|
|
|
|
emit testTreeModelChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestTreeModel::removeAllTestToolItems()
|
|
|
|
|
{
|
2020-10-29 14:27:55 +01:00
|
|
|
for (ITestTreeItem *item : testToolRootNodes()) {
|
2016-06-01 16:22:50 +02:00
|
|
|
item->removeChildren();
|
2020-10-19 14:46:21 +02:00
|
|
|
if (item->checked() == Qt::PartiallyChecked)
|
|
|
|
|
item->setData(0, Qt::Checked, Qt::CheckStateRole);
|
2017-10-23 10:33:27 +02:00
|
|
|
}
|
2014-11-06 16:01:06 +01:00
|
|
|
emit testTreeModelChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-01 16:22:50 +02:00
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
// we're inside tests - so use some internal knowledge to make testing easier
|
2019-05-21 13:39:00 +02:00
|
|
|
static TestTreeItem *qtRootNode()
|
2015-02-05 16:07:45 +01:00
|
|
|
{
|
2020-06-26 13:59:38 +02:00
|
|
|
auto id = Utils::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtTest");
|
2020-03-13 13:54:33 +01:00
|
|
|
return TestFrameworkManager::frameworkForId(id)->rootNode();
|
2016-06-01 16:22:50 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-21 13:39:00 +02:00
|
|
|
static TestTreeItem *quickRootNode()
|
2016-06-01 16:22:50 +02:00
|
|
|
{
|
2020-06-26 13:59:38 +02:00
|
|
|
auto id = Utils::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtQuickTest");
|
2020-03-13 13:54:33 +01:00
|
|
|
return TestFrameworkManager::frameworkForId(id)->rootNode();
|
2016-06-01 16:22:50 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-21 13:39:00 +02:00
|
|
|
static TestTreeItem *gtestRootNode()
|
2016-06-01 16:22:50 +02:00
|
|
|
{
|
2020-06-26 13:59:38 +02:00
|
|
|
auto id = Utils::Id(Constants::FRAMEWORK_PREFIX).withSuffix("GTest");
|
2020-03-13 13:54:33 +01:00
|
|
|
return TestFrameworkManager::frameworkForId(id)->rootNode();
|
2015-02-05 16:07:45 +01:00
|
|
|
}
|
|
|
|
|
|
2019-05-21 13:39:00 +02:00
|
|
|
static TestTreeItem *boostTestRootNode()
|
|
|
|
|
{
|
2020-06-26 13:59:38 +02:00
|
|
|
auto id = Utils::Id(Constants::FRAMEWORK_PREFIX).withSuffix("Boost");
|
2020-03-13 13:54:33 +01:00
|
|
|
return TestFrameworkManager::frameworkForId(id)->rootNode();
|
2019-05-21 13:39:00 +02:00
|
|
|
}
|
|
|
|
|
|
2015-02-10 14:20:43 +01:00
|
|
|
int TestTreeModel::autoTestsCount() const
|
|
|
|
|
{
|
2016-06-01 16:22:50 +02:00
|
|
|
TestTreeItem *rootNode = qtRootNode();
|
|
|
|
|
return rootNode ? rootNode->childCount() : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TestTreeModel::hasUnnamedQuickTests(const TestTreeItem *rootNode) const
|
|
|
|
|
{
|
|
|
|
|
for (int row = 0, count = rootNode->childCount(); row < count; ++row) {
|
2018-04-17 15:53:06 +02:00
|
|
|
if (rootNode->childAt(row)->name().isEmpty())
|
2016-06-01 16:22:50 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestTreeItem *TestTreeModel::unnamedQuickTests() const
|
|
|
|
|
{
|
|
|
|
|
TestTreeItem *rootNode = quickRootNode();
|
|
|
|
|
if (!rootNode)
|
2017-02-13 10:05:06 +01:00
|
|
|
return nullptr;
|
2016-06-01 16:22:50 +02:00
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
return rootNode->findFirstLevelChildItem([](TestTreeItem *it) { return it->name().isEmpty(); });
|
2015-02-10 14:20:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int TestTreeModel::namedQuickTestsCount() const
|
|
|
|
|
{
|
2016-06-01 16:22:50 +02:00
|
|
|
TestTreeItem *rootNode = quickRootNode();
|
|
|
|
|
return rootNode
|
|
|
|
|
? rootNode->childCount() - (hasUnnamedQuickTests(rootNode) ? 1 : 0)
|
2015-02-10 14:20:43 +01:00
|
|
|
: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int TestTreeModel::unnamedQuickTestsCount() const
|
|
|
|
|
{
|
|
|
|
|
if (TestTreeItem *unnamed = unnamedQuickTests())
|
|
|
|
|
return unnamed->childCount();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2015-11-02 11:06:19 +01:00
|
|
|
|
|
|
|
|
int TestTreeModel::dataTagsCount() const
|
|
|
|
|
{
|
2016-06-01 16:22:50 +02:00
|
|
|
TestTreeItem *rootNode = qtRootNode();
|
|
|
|
|
if (!rootNode)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2015-11-02 11:06:19 +01:00
|
|
|
int dataTagCount = 0;
|
2020-10-12 14:11:10 +02:00
|
|
|
rootNode->forFirstLevelChildren([&dataTagCount](ITestTreeItem *classItem) {
|
|
|
|
|
classItem->forFirstLevelChildren([&dataTagCount](ITestTreeItem *functionItem) {
|
2015-11-02 11:06:19 +01:00
|
|
|
dataTagCount += functionItem->childCount();
|
2018-04-17 15:53:06 +02:00
|
|
|
});
|
|
|
|
|
});
|
2015-11-02 11:06:19 +01:00
|
|
|
return dataTagCount;
|
|
|
|
|
}
|
2015-12-10 13:46:04 +01:00
|
|
|
|
|
|
|
|
int TestTreeModel::gtestNamesCount() const
|
|
|
|
|
{
|
2016-06-01 16:22:50 +02:00
|
|
|
TestTreeItem *rootNode = gtestRootNode();
|
|
|
|
|
return rootNode ? rootNode->childCount() : 0;
|
2015-12-10 13:46:04 +01:00
|
|
|
}
|
|
|
|
|
|
2016-01-13 09:10:11 +01:00
|
|
|
QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const
|
2015-12-10 13:46:04 +01:00
|
|
|
{
|
2016-01-13 09:10:11 +01:00
|
|
|
QMultiMap<QString, int> result;
|
2015-12-10 13:46:04 +01:00
|
|
|
|
2016-06-01 16:22:50 +02:00
|
|
|
if (TestTreeItem *rootNode = gtestRootNode()) {
|
2020-10-12 14:11:10 +02:00
|
|
|
rootNode->forFirstLevelChildren([&result](ITestTreeItem *child) {
|
2018-04-17 15:53:06 +02:00
|
|
|
result.insert(child->name(), child->childCount());
|
|
|
|
|
});
|
2015-12-10 13:46:04 +01:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2019-05-21 13:39:00 +02:00
|
|
|
|
|
|
|
|
int TestTreeModel::boostTestNamesCount() const
|
|
|
|
|
{
|
|
|
|
|
TestTreeItem *rootNode = boostTestRootNode();
|
|
|
|
|
return rootNode ? rootNode->childCount() : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-25 16:29:04 +01:00
|
|
|
QMap<QString, int> TestTreeModel::boostTestSuitesAndTests() const
|
2019-05-21 13:39:00 +02:00
|
|
|
{
|
2020-03-25 16:29:04 +01:00
|
|
|
QMap<QString, int> result;
|
2019-05-21 13:39:00 +02:00
|
|
|
|
|
|
|
|
if (TestTreeItem *rootNode = boostTestRootNode()) {
|
2020-10-12 14:11:10 +02:00
|
|
|
rootNode->forFirstLevelChildItems([&result](TestTreeItem *child) {
|
2020-03-25 16:29:04 +01:00
|
|
|
result.insert(child->name() + '|' + child->proFile(), child->childCount());
|
2019-05-21 13:39:00 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-10 14:20:43 +01:00
|
|
|
#endif
|
|
|
|
|
|
2014-11-11 17:30:34 +01:00
|
|
|
/***************************** Sort/Filter Model **********************************/
|
|
|
|
|
|
|
|
|
|
TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent)
|
2017-12-05 07:50:26 +01:00
|
|
|
: QSortFilterProxyModel(parent)
|
2014-11-11 17:30:34 +01:00
|
|
|
{
|
|
|
|
|
setSourceModel(sourceModel);
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-28 16:42:14 +02:00
|
|
|
void TestTreeSortFilterModel::setSortMode(TestTreeItem::SortMode sortMode)
|
2014-11-11 17:30:34 +01:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
{
|
2016-04-28 16:42:14 +02:00
|
|
|
// root items keep the intended order
|
2020-10-12 14:11:10 +02:00
|
|
|
const ITestTreeItem *leftItem = static_cast<ITestTreeItem *>(left.internalPointer());
|
|
|
|
|
if (leftItem->type() == ITestTreeItem::Root)
|
2014-11-11 17:30:34 +01:00
|
|
|
return left.row() > right.row();
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
const ITestTreeItem *rightItem = static_cast<ITestTreeItem *>(right.internalPointer());
|
2016-04-28 16:42:14 +02:00
|
|
|
return leftItem->lessThan(rightItem, m_sortMode);
|
2014-11-11 17:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
|
|
|
{
|
2017-12-05 07:50:26 +01:00
|
|
|
const QModelIndex index = sourceModel()->index(sourceRow, 0,sourceParent);
|
2014-11-11 17:30:34 +01:00
|
|
|
if (!index.isValid())
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-10-12 14:11:10 +02:00
|
|
|
const ITestTreeItem *item = static_cast<ITestTreeItem *>(index.internalPointer());
|
2014-11-11 17:30:34 +01:00
|
|
|
|
2014-11-13 12:31:58 +01:00
|
|
|
switch (item->type()) {
|
2020-10-12 14:11:10 +02:00
|
|
|
case ITestTreeItem::TestDataFunction:
|
2014-11-13 12:31:58 +01:00
|
|
|
return m_filterMode & ShowTestData;
|
2020-10-12 14:11:10 +02:00
|
|
|
case ITestTreeItem::TestSpecialFunction:
|
2014-11-13 12:31:58 +01:00
|
|
|
return m_filterMode & ShowInitAndCleanup;
|
|
|
|
|
default:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2014-11-11 17:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
2014-10-07 12:30:54 +02:00
|
|
|
} // namespace Autotest
|