Files
qt-creator/src/plugins/autotest/testtreemodel.cpp

602 lines
20 KiB
C++
Raw Normal View History

2014-10-07 12:30:54 +02:00
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
2014-10-07 12:30:54 +02:00
**
** This file is part of Qt Creator.
2014-10-07 12:30:54 +02: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
** 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
**
** 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
**
****************************************************************************/
#include "autotestconstants.h"
#include "autotestplugin.h"
2014-10-07 12:30:54 +02:00
#include "testcodeparser.h"
#include "testframeworkmanager.h"
#include "testsettings.h"
2014-10-07 12:30:54 +02:00
#include "testtreeitem.h"
#include "testtreemodel.h"
2014-10-21 13:10:37 +02:00
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
2014-10-21 13:10:37 +02:00
#include <texteditor/texteditor.h>
#include <utils/qtcassert.h>
2014-10-21 13:10:37 +02:00
2014-10-07 12:30:54 +02:00
namespace Autotest {
namespace Internal {
TestTreeModel::TestTreeModel(QObject *parent) :
TreeModel<>(parent),
m_parser(new TestCodeParser(this))
2014-10-07 12:30:54 +02:00
{
connect(m_parser, &TestCodeParser::aboutToPerformFullParse, this,
&TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testParseResultReady,
this, &TestTreeModel::onParseResultReady, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::parsingFinished,
this, &TestTreeModel::sweep, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::parsingFailed,
this, &TestTreeModel::sweep, Qt::QueuedConnection);
setupParsingConnections();
2014-10-07 12:30:54 +02:00
}
static TestTreeModel *s_instance = nullptr;
2014-10-07 12:30:54 +02:00
TestTreeModel *TestTreeModel::instance()
{
if (!s_instance)
s_instance = new TestTreeModel;
return s_instance;
2014-10-07 12:30:54 +02:00
}
TestTreeModel::~TestTreeModel()
{
removeTestRootNodes();
s_instance = nullptr;
2014-10-07 12:30:54 +02:00
}
void TestTreeModel::setupParsingConnections()
{
static bool connectionsInitialized = false;
if (connectionsInitialized)
return;
m_parser->setDirty();
m_parser->setState(TestCodeParser::Idle);
ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance();
connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged,
m_parser, &TestCodeParser::onStartupProjectChanged);
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
connect(cppMM, &CppTools::CppModelManager::documentUpdated,
m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles,
this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
connect(cppMM, &CppTools::CppModelManager::projectPartsUpdated,
m_parser, &TestCodeParser::onProjectPartsUpdated);
QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance();
connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
this, &TestTreeModel::removeFiles, Qt::QueuedConnection);
connectionsInitialized = true;
}
2014-10-07 12:30:54 +02:00
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
if (item && item->setData(index.column(), value, role)) {
emit dataChanged(index, index);
if (role == Qt::CheckStateRole) {
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
}
if (item->parent() != rootItem() && item->parentItem()->checked() != checked)
revalidateCheckState(item->parentItem()); // handle parent too
return 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;
TestTreeItem *item = static_cast<TestTreeItem *>(itemForIndex(index));
return item->flags(index.column());
2014-10-07 12:30:54 +02:00
}
bool TestTreeModel::hasTests() const
{
for (Utils::TreeItem *frameworkRoot : *rootItem()) {
if (frameworkRoot->hasChildren())
return true;
}
return false;
2014-10-07 12:30:54 +02:00
}
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
QList<TestConfiguration *> result;
for (Utils::TreeItem *frameworkRoot : *rootItem())
result.append(static_cast<TestTreeItem *>(frameworkRoot)->getAllTestConfigurations());
return result;
}
QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
QList<TestConfiguration *> result;
for (Utils::TreeItem *frameworkRoot : *rootItem())
result.append(static_cast<TestTreeItem *>(frameworkRoot)->getSelectedTestConfigurations());
return result;
}
QList<TestTreeItem *> TestTreeModel::testItemsByName(TestTreeItem *root, const QString &testName)
{
QList<TestTreeItem *> result;
for (int row = 0, count = root->childCount(); row < count; ++row){
TestTreeItem *node = root->childItem(row);
if (node->type() == TestTreeItem::TestCase) {
if (node->name() == testName) {
result << node;
continue; // prioritize Tests over TestCases
}
TestTreeItem *testCase = node->findChildBy([testName](const TestTreeItem *it) {
QTC_ASSERT(it, return false);
return it->type() == TestTreeItem::TestFunctionOrSet && it->name() == testName;
}); // collect only actual tests, not special functions like init, cleanup etc,
if (!testCase)
continue;
result << testCase;
} else {
result << testItemsByName(node, testName);
}
}
return result;
}
QList<TestTreeItem *> TestTreeModel::testItemsByName(const QString &testName)
{
QList<TestTreeItem *> result;
for (Utils::TreeItem *frameworkRoot : *rootItem())
result << testItemsByName(static_cast<TestTreeItem *>(frameworkRoot), testName);
return result;
}
void TestTreeModel::syncTestFrameworks()
{
// remove all currently registered
removeTestRootNodes();
TestFrameworkManager *frameworkManager = TestFrameworkManager::instance();
QVector<Core::Id> sortedIds = frameworkManager->sortedActiveFrameworkIds();
for (const Core::Id &id : sortedIds)
rootItem()->appendChild(frameworkManager->rootNodeForTestFramework(id));
m_parser->syncTestFrameworks(sortedIds);
emit updatedActiveFrameworks(sortedIds.size());
2014-11-06 16:01:06 +01:00
}
void TestTreeModel::rebuild(const QList<Core::Id> &frameworkIds)
{
TestFrameworkManager *frameworkManager = TestFrameworkManager::instance();
for (const Core::Id &id : frameworkIds) {
TestTreeItem *frameworkRoot = frameworkManager->rootNodeForTestFramework(id);
const bool groupingEnabled = TestFrameworkManager::instance()->groupingEnabled(id);
for (int row = frameworkRoot->childCount() - 1; row >= 0; --row) {
auto testItem = frameworkRoot->childItem(row);
if (!groupingEnabled && testItem->type() == TestTreeItem::GroupNode) {
// do not re-insert the GroupNode, but process its children and delete it afterwards
for (int childRow = testItem->childCount() - 1; childRow >= 0; --childRow) {
// FIXME should this be done recursively until we have a non-GroupNode?
TestTreeItem *childTestItem = testItem->childItem(childRow);
takeItem(childTestItem);
insertItemInParent(childTestItem, frameworkRoot, groupingEnabled);
}
delete takeItem(testItem);
} else {
takeItem(testItem);
insertItemInParent(testItem, frameworkRoot, groupingEnabled);
}
}
}
}
void TestTreeModel::removeFiles(const QStringList &files)
2014-10-07 12:30:54 +02:00
{
for (const QString &file : files)
markForRemoval(file);
sweep();
}
void TestTreeModel::markAllForRemoval()
{
for (Utils::TreeItem *frameworkRoot : *rootItem()) {
for (Utils::TreeItem *item : *frameworkRoot)
static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);
}
}
void TestTreeModel::markForRemoval(const QString &filePath)
{
if (filePath.isEmpty())
return;
for (Utils::TreeItem *frameworkRoot : *rootItem()) {
TestTreeItem *root = static_cast<TestTreeItem *>(frameworkRoot);
for (int childRow = root->childCount() - 1; childRow >= 0; --childRow) {
TestTreeItem *child = root->childItem(childRow);
child->markForRemovalRecursively(filePath);
}
}
}
void TestTreeModel::sweep()
{
for (Utils::TreeItem *frameworkRoot : *rootItem()) {
TestTreeItem *root = static_cast<TestTreeItem *>(frameworkRoot);
sweepChildren(root);
}
// even if nothing has changed by the sweeping we might had parse which added or modified items
emit testTreeModelChanged();
#ifdef WITH_TESTS
if (m_parser->state() == TestCodeParser::Idle && !m_parser->furtherParsingExpected())
emit sweepingDone();
#endif
}
/**
* @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->type() != TestTreeItem::Root && child->markedForRemoval()) {
destroyItem(child);
revalidateCheckState(item);
hasChanged = true;
} else if (child->hasChildren()) {
hasChanged |= sweepChildren(child);
if (!child->hasChildren() && child->removeOnSweepIfEmpty()) {
destroyItem(child);
revalidateCheckState(item);
}
} else {
hasChanged |= child->newlyAdded();
}
}
return hasChanged;
}
void TestTreeModel::insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled)
{
TestTreeItem *parentNode = root;
if (groupingEnabled) {
parentNode = root->findChildBy([item] (const TestTreeItem *it) {
return it->isGroupNodeFor(item);
});
if (!parentNode) {
parentNode = item->createParentGroupNode();
if (!parentNode) // we might not get a group node at all
parentNode = root;
else
root->appendChild(parentNode);
}
}
parentNode->appendChild(item);
if (item->checked() != parentNode->checked())
revalidateCheckState(parentNode);
}
void TestTreeModel::revalidateCheckState(TestTreeItem *item)
{
QTC_ASSERT(item, return);
const TestTreeItem::Type type = item->type();
if (type == TestTreeItem::TestSpecialFunction || type == TestTreeItem::TestDataFunction
|| type == TestTreeItem::TestDataTag) {
return;
}
const Qt::CheckState oldState = (Qt::CheckState)item->data(0, Qt::CheckStateRole).toInt();
Qt::CheckState newState = Qt::Checked;
bool foundChecked = false;
bool foundUnchecked = false;
bool foundPartiallyChecked = false;
for (int row = 0, count = item->childCount(); row < count; ++row) {
TestTreeItem *child = item->childItem(row);
switch (child->type()) {
case TestTreeItem::TestDataFunction:
case TestTreeItem::TestSpecialFunction:
continue;
default:
break;
}
foundChecked |= (child->checked() == Qt::Checked);
foundUnchecked |= (child->checked() == Qt::Unchecked);
foundPartiallyChecked |= (child->checked() == Qt::PartiallyChecked);
if (foundPartiallyChecked || (foundChecked && foundUnchecked)) {
newState = Qt::PartiallyChecked;
break;
}
}
if (newState != Qt::PartiallyChecked)
newState = foundUnchecked ? Qt::Unchecked : Qt::Checked;
if (oldState != newState) {
item->setData(0, newState, Qt::CheckStateRole);
emit dataChanged(item->index(), item->index());
if (item->parent() != rootItem() && item->parentItem()->checked() != newState)
revalidateCheckState(item->parentItem());
}
}
void TestTreeModel::onParseResultReady(const TestParseResultPtr result)
{
TestTreeItem *rootNode
= TestFrameworkManager::instance()->rootNodeForTestFramework(result->frameworkId);
QTC_ASSERT(rootNode, return);
handleParseResult(result.data(), rootNode);
}
void TestTreeModel::handleParseResult(const TestParseResult *result, TestTreeItem *parentNode)
{
const bool groupingEnabled =
TestFrameworkManager::instance()->groupingEnabled(result->frameworkId);
// lookup existing items
if (TestTreeItem *toBeModified = parentNode->find(result)) {
// found existing item... Do not remove
toBeModified->markForRemoval(false);
// 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);
}
}
// modify and when content has changed inform ui
if (toBeModified->modify(result)) {
const QModelIndex &idx = indexForItem(toBeModified);
emit dataChanged(idx, idx);
}
// recursively handle children of this item
for (const TestParseResult *child : result->children)
handleParseResult(child, toBeModified);
return;
}
// if there's no matching item, add the new one
TestTreeItem *newItem = result->createTestTreeItem();
QTC_ASSERT(newItem, return);
insertItemInParent(newItem, parentNode, groupingEnabled);
}
void TestTreeModel::removeAllTestItems()
2014-11-06 16:01:06 +01:00
{
for (Utils::TreeItem *item : *rootItem()) {
item->removeChildren();
TestTreeItem *testTreeItem = static_cast<TestTreeItem *>(item);
if (testTreeItem->checked() == Qt::PartiallyChecked)
testTreeItem->setData(0, Qt::Checked, Qt::CheckStateRole);
}
2014-11-06 16:01:06 +01:00
emit testTreeModelChanged();
}
void TestTreeModel::removeTestRootNodes()
{
const Utils::TreeItem *invisibleRoot = rootItem();
const int frameworkRootCount = invisibleRoot ? invisibleRoot->childCount() : 0;
for (int row = frameworkRootCount - 1; row >= 0; --row) {
Utils::TreeItem *item = invisibleRoot->childAt(row);
item->removeChildren();
takeItem(item); // do NOT delete the item as it's still a ptr held by TestFrameworkManager
}
}
#ifdef WITH_TESTS
// we're inside tests - so use some internal knowledge to make testing easier
TestTreeItem *qtRootNode()
{
return TestFrameworkManager::instance()->rootNodeForTestFramework(
Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtTest"));
}
TestTreeItem *quickRootNode()
{
return TestFrameworkManager::instance()->rootNodeForTestFramework(
Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("QtQuickTest"));
}
TestTreeItem *gtestRootNode()
{
return TestFrameworkManager::instance()->rootNodeForTestFramework(
Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix("GTest"));
}
int TestTreeModel::autoTestsCount() const
{
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) {
if (rootNode->childItem(row)->name().isEmpty())
return true;
}
return false;
}
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
TestTreeItem *rootNode = quickRootNode();
if (!rootNode)
return nullptr;
for (int row = 0, count = rootNode->childCount(); row < count; ++row) {
TestTreeItem *child = rootNode->childItem(row);
if (child->name().isEmpty())
return child;
}
return nullptr;
}
int TestTreeModel::namedQuickTestsCount() const
{
TestTreeItem *rootNode = quickRootNode();
return rootNode
? rootNode->childCount() - (hasUnnamedQuickTests(rootNode) ? 1 : 0)
: 0;
}
int TestTreeModel::unnamedQuickTestsCount() const
{
if (TestTreeItem *unnamed = unnamedQuickTests())
return unnamed->childCount();
return 0;
}
int TestTreeModel::dataTagsCount() const
{
TestTreeItem *rootNode = qtRootNode();
if (!rootNode)
return 0;
int dataTagCount = 0;
for (Utils::TreeItem *item : *rootNode) {
TestTreeItem *classItem = static_cast<TestTreeItem *>(item);
for (Utils::TreeItem *functionItem : *classItem)
dataTagCount += functionItem->childCount();
}
return dataTagCount;
}
int TestTreeModel::gtestNamesCount() const
{
TestTreeItem *rootNode = gtestRootNode();
return rootNode ? rootNode->childCount() : 0;
}
QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const
{
QMultiMap<QString, int> result;
if (TestTreeItem *rootNode = gtestRootNode()) {
for (int row = 0, count = rootNode->childCount(); row < count; ++row) {
const TestTreeItem *current = rootNode->childItem(row);
result.insert(current->name(), current->childCount());
}
}
return result;
}
#endif
/***************************** Sort/Filter Model **********************************/
TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent)
: QSortFilterProxyModel(parent)
{
setSourceModel(sourceModel);
}
void TestTreeSortFilterModel::setSortMode(TestTreeItem::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
const TestTreeItem *leftItem = static_cast<TestTreeItem *>(left.internalPointer());
if (leftItem->type() == TestTreeItem::Root)
return left.row() > right.row();
const TestTreeItem *rightItem = static_cast<TestTreeItem *>(right.internalPointer());
return leftItem->lessThan(rightItem, m_sortMode);
}
bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const QModelIndex index = sourceModel()->index(sourceRow, 0,sourceParent);
if (!index.isValid())
return false;
2014-11-13 12:31:58 +01:00
const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
2014-11-13 12:31:58 +01:00
switch (item->type()) {
case TestTreeItem::TestDataFunction:
2014-11-13 12:31:58 +01:00
return m_filterMode & ShowTestData;
case TestTreeItem::TestSpecialFunction:
2014-11-13 12:31:58 +01:00
return m_filterMode & ShowInitAndCleanup;
default:
return true;
}
}
2014-10-07 12:30:54 +02:00
} // namespace Internal
} // namespace Autotest