Files
qt-creator/plugins/autotest/testtreemodel.cpp
Christian Stenger 7c06e4b768 Be more generic regarding supported project types
This removes the hard dependency on QmakeProjectManager.
Furthermore now unneeded code is removed and some parts
of the parsing are slightly modified to support other
project types than qmake based projects.

Change-Id: I1c23056d5a444ddea857e10fdb71264eb6ecc269
Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
Reviewed-by: Niels Weber <niels.weber@theqtcompany.com>
2015-08-06 14:53:45 +03:00

959 lines
35 KiB
C++

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at
** http://www.qt.io/contact-us
**
** This file is part of the Qt Creator Enterprise Auto Test Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
#include "autotestconstants.h"
#include "testcodeparser.h"
#include "testtreeitem.h"
#include "testtreemodel.h"
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <texteditor/texteditor.h>
#include <utils/qtcassert.h>
#include <QIcon>
namespace Autotest {
namespace Internal {
TestTreeModel::TestTreeModel(QObject *parent) :
QAbstractItemModel(parent),
m_rootItem(new TestTreeItem(QString(), QString(), TestTreeItem::ROOT)),
m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::ROOT, m_rootItem)),
m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT, m_rootItem)),
m_parser(new TestCodeParser(this)),
m_connectionsInitialized(false)
{
m_rootItem->appendChild(m_autoTestRootItem);
m_rootItem->appendChild(m_quickTestRootItem);
connect(m_parser, &TestCodeParser::cacheCleared, this,
&TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemCreated,
this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemsCreated,
this, &TestTreeModel::addTestTreeItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemModified,
this, &TestTreeModel::modifyTestTreeItem, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemsRemoved,
this, &TestTreeModel::removeTestTreeItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated,
this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved,
this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection);
// CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance();
// if (cppMM) {
// // replace later on by
// // cppMM->registerAstProcessor([this](const CplusPlus::Document::Ptr &doc,
// // const CPlusPlus::Snapshot &snapshot) {
// // checkForQtTestStuff(doc, snapshot);
// // });
// connect(cppMM, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
// this, SLOT(checkForQtTestStuff(CPlusPlus::Document::Ptr)),
// Qt::DirectConnection);
// }
}
static TestTreeModel *m_instance = 0;
TestTreeModel *TestTreeModel::instance()
{
if (!m_instance)
m_instance = new TestTreeModel;
return m_instance;
}
TestTreeModel::~TestTreeModel()
{
delete m_rootItem;
m_instance = 0;
}
void TestTreeModel::enableParsing()
{
m_refCounter.ref();
if (!m_connectionsInitialized)
m_parser->setDirty();
m_parser->setState(TestCodeParser::Idle);
if (m_connectionsInitialized)
return;
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,
m_parser, &TestCodeParser::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,
m_parser, &TestCodeParser::removeFiles, Qt::QueuedConnection);
m_connectionsInitialized = true;
}
void TestTreeModel::disableParsing()
{
if (!m_refCounter.deref())
m_parser->setState(TestCodeParser::Disabled);
}
QModelIndex TestTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TestTreeItem *parentItem = parent.isValid()
? static_cast<TestTreeItem *>(parent.internalPointer())
: m_rootItem;
TestTreeItem *childItem = parentItem->child(row);
return childItem ? createIndex(row, column, childItem) : QModelIndex();
}
QModelIndex TestTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TestTreeItem *childItem = static_cast<TestTreeItem *>(index.internalPointer());
TestTreeItem *parentItem = childItem->parent();
if (parentItem == m_rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
bool TestTreeModel::hasChildren(const QModelIndex &parent) const
{
if (!parent.isValid())
return true;
TestTreeItem *item = static_cast<TestTreeItem *>(parent.internalPointer());
return item->childCount() > 0;
}
int TestTreeModel::rowCount(const QModelIndex &parent) const
{
TestTreeItem *parentItem = parent.isValid()
? static_cast<TestTreeItem *>(parent.internalPointer()) : m_rootItem;
return parentItem->childCount();
}
int TestTreeModel::columnCount(const QModelIndex &) const
{
return 1;
}
static QIcon testTreeIcon(TestTreeItem::Type type)
{
static QIcon icons[3] = {
QIcon(),
QIcon(QLatin1String(":/images/class.png")),
QIcon(QLatin1String(":/images/func.png"))
};
if (type >= 3)
return icons[2];
return icons[type];
}
QVariant TestTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
if (!item)
return QVariant();
if (role == Qt::DisplayRole) {
if ((item == m_autoTestRootItem && m_autoTestRootItem->childCount() == 0)
|| (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) {
return QString(item->name() + tr(" (none)"));
} else {
if (item->name().isEmpty())
return tr(Constants::UNNAMED_QUICKTESTS);
return item->name();
}
return QVariant(); // TODO ?
}
switch(role) {
case Qt::ToolTipRole:
if (item->type() == TestTreeItem::TEST_CLASS && item->name().isEmpty())
return tr("<p>Give all test cases a name to ensure correct behavior "
"when running test cases and to be able to select them.</p>");
return item->filePath();
case Qt::DecorationRole:
return testTreeIcon(item->type());
case Qt::CheckStateRole:
switch (item->type()) {
case TestTreeItem::ROOT:
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
return QVariant();
case TestTreeItem::TEST_CLASS:
if (item->name().isEmpty())
return QVariant();
else
return item->checked();
case TestTreeItem::TEST_FUNCTION:
if (TestTreeItem *parent = item->parent())
return parent->name().isEmpty() ? QVariant() : item->checked();
else
return item->checked();
default:
return item->checked();
}
case LinkRole: {
QVariant itemLink;
TextEditor::TextEditorWidget::Link link(item->filePath(), item->line(), item->column());
itemLink.setValue(link);
return itemLink;
}
case ItalicRole:
switch (item->type()) {
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
return true;
case TestTreeItem::TEST_CLASS:
return item->name().isEmpty();
case TestTreeItem::TEST_FUNCTION:
if (TestTreeItem *parent = item->parent())
return parent->name().isEmpty();
else
return false;
default:
return false;
}
case TypeRole:
return item->type();
}
// TODO ?
return QVariant();
}
bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
if (role == Qt::CheckStateRole) {
TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
Qt::CheckState old = item->checked();
item->setChecked((Qt::CheckState)value.toInt());
if (item->checked() != old) {
switch(item->type()) {
case TestTreeItem::TEST_CLASS:
emit dataChanged(index, index);
if (item->childCount() > 0) {
emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
}
break;
case TestTreeItem::TEST_FUNCTION:
emit dataChanged(index, index);
emit dataChanged(index.parent(), index.parent());
break;
default: // avoid warning regarding unhandled enum member
break;
}
return true;
}
}
return false;
}
Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
switch(item->type()) {
case TestTreeItem::TEST_CLASS:
if (item->name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
case TestTreeItem::TEST_FUNCTION:
if (item->parent()->name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
case TestTreeItem::ROOT:
return Qt::ItemIsEnabled;
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
default:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
bool TestTreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (!parent.isValid())
return false;
TestTreeItem *parentItem = static_cast<TestTreeItem *>(parent.internalPointer());
if (!parentItem)
return false;
bool subItemsSuccess = true;
bool itemSuccess = true;
for (int i = row + count - 1; i >= row; --i) {
QModelIndex child = index(i, 0, parent);
subItemsSuccess &= removeRows(0, rowCount(child), child);
beginRemoveRows(parent, i, i);
itemSuccess &= parentItem->removeChild(i);
endRemoveRows();
}
return subItemsSuccess && itemSuccess;
}
bool TestTreeModel::hasTests() const
{
return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0;
}
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project)
return result;
// get all Auto Tests
for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
const TestTreeItem *child = m_autoTestRootItem->child(row);
TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(),
child->childCount());
tc->setMainFilePath(child->filePath());
tc->setProject(project);
result << tc;
}
// get all Quick Tests
QMap<QString, int> foundMains;
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
// unnamed Quick Tests must be handled separately
if (child->name().isEmpty()) {
for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) {
const TestTreeItem *grandChild = child->child(childRow);
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));
tc->setMainFilePath(mainFile);
tc->setProject(project);
result << tc;
}
return result;
}
QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project)
return result;
TestConfiguration *testConfiguration = 0;
for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_autoTestRootItem->child(row);
switch (child->checked()) {
case Qt::Unchecked:
continue;
case Qt::Checked:
testConfiguration = new TestConfiguration(child->name(), QStringList(), child->childCount());
testConfiguration->setMainFilePath(child->filePath());
testConfiguration->setProject(project);
result << testConfiguration;
continue;
case Qt::PartiallyChecked:
default:
const QString childName = child->name();
int grandChildCount = child->childCount();
QStringList testCases;
for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
const TestTreeItem *grandChild = child->child(grandChildRow);
if (grandChild->checked() == Qt::Checked)
testCases << grandChild->name();
}
testConfiguration = new TestConfiguration(childName, testCases);
testConfiguration->setMainFilePath(child->filePath());
testConfiguration->setProject(project);
result << testConfiguration;
}
}
// 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;
if (TestTreeItem *unnamed = unnamedQuickTests()) {
for (int childRow = 0, ccount = unnamed->childCount(); childRow < ccount; ++ childRow) {
const TestTreeItem *grandChild = unnamed->child(childRow);
const QString mainFile = grandChild->mainFile();
if (foundMains.contains(mainFile)) {
QTC_ASSERT(testConfiguration,
qWarning() << "Illegal state (unnamed Quick Test listed as named)";
return QList<TestConfiguration *>());
foundMains[mainFile]->setTestCaseCount(testConfiguration->testCaseCount() + 1);
} else {
testConfiguration = new TestConfiguration(QString(), QStringList());
testConfiguration->setTestCaseCount(1);
testConfiguration->setUnnamedOnly(true);
testConfiguration->setMainFilePath(mainFile);
testConfiguration->setProject(project);
foundMains.insert(mainFile, testConfiguration);
}
}
}
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
// 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) {
const TestTreeItem *grandChild = child->child(grandChildRow);
if (grandChild->checked() == Qt::Checked)
testFunctions << child->name() + QLatin1String("::") + grandChild->name();
}
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());
tc->setUnnamedOnly(false);
} else {
oldFunctions << testFunctions;
tc->setTestCases(oldFunctions);
}
} else {
tc = new TestConfiguration(QString(), testFunctions);
tc->setMainFilePath(child->mainFile());
tc->setProject(project);
foundMains.insert(child->mainFile(), tc);
}
break;
}
}
foreach (TestConfiguration *config, foundMains.values())
if (!config->unnamedOnly())
result << config;
return result;
}
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()) {
case TestTreeItem::TEST_CLASS: {
if (item->parent() == m_quickTestRootItem) {
// Quick Test TestCase
QStringList testFunctions;
for (int row = 0, count = item->childCount(); row < count; ++row) {
testFunctions << item->name() + QLatin1String("::") + item->child(row)->name();
}
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;
}
case TestTreeItem::TEST_FUNCTION: {
const TestTreeItem *parent = item->parent();
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;
}
// not supported items
default:
return 0;
}
return config;
}
QString TestTreeModel::getMainFileForUnnamedQuickTest(const QString &qmlFile) const
{
const TestTreeItem *unnamed = unnamedQuickTests();
const int count = unnamed ? unnamed->childCount() : 0;
for (int row = 0; row < count; ++row) {
const TestTreeItem *child = unnamed->child(row);
if (qmlFile == child->filePath())
return child->mainFile();
}
return QString();
}
void TestTreeModel::qmlFilesForMainFile(const QString &mainFile, QSet<QString> *filePaths) const
{
TestTreeItem *unnamed = unnamedQuickTests();
if (!unnamed)
return;
for (int i = 0; i < unnamed->childCount(); ++i) {
const TestTreeItem *child = unnamed->child(i);
if (child->mainFile() == mainFile)
filePaths->insert(child->filePath());
}
}
QList<QString> TestTreeModel::getUnnamedQuickTestFunctions() const
{
TestTreeItem *unnamed = unnamedQuickTests();
if (unnamed)
return unnamed->getChildNames();
return QList<QString>();
}
QSet<QString> TestTreeModel::qmlFilesForProFile(const QString &proFile) const
{
QSet<QString> filePaths;
CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
if (TestTreeItem *unnamed = unnamedQuickTests()) {
for (int i = 0; i < unnamed->childCount(); ++i) {
const TestTreeItem *child = unnamed->child(i);
QList<CppTools::ProjectPart::Ptr> ppList = modelManager->projectPart(child->mainFile());
if (ppList.size() && ppList.at(0)->projectFile == proFile)
filePaths.insert(child->filePath());
}
}
return filePaths;
}
bool TestTreeModel::hasUnnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row)
if (m_quickTestRootItem->child(row)->name().isEmpty())
return true;
return false;
}
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
if (child->name().isEmpty())
return child;
}
return 0;
}
void TestTreeModel::removeUnnamedQuickTests(const QString &filePath)
{
TestTreeItem *unnamedQT = unnamedQuickTests();
if (!unnamedQT)
return;
const QModelIndex unnamedQTIndex = index(1, 0).child(unnamedQT->row(), 0);
for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) {
const TestTreeItem *child = unnamedQT->child(childRow);
if (filePath == child->filePath())
removeRow(childRow, unnamedQTIndex);
}
if (unnamedQT->childCount() == 0)
removeRow(unnamedQT->row(), unnamedQTIndex.parent());
emit testTreeModelChanged();
}
void TestTreeModel::addTestTreeItem(const TestTreeItem &item, TestTreeModel::Type type)
{
TestTreeItem *parent = rootItemForType(type);
QModelIndex index = rootIndexForType(type);
TestTreeItem *toBeAdded = new TestTreeItem(item);
toBeAdded->setParent(parent);
beginInsertRows(index, parent->childCount(), parent->childCount());
parent->appendChild(toBeAdded);
endInsertRows();
emit testTreeModelChanged();
}
void TestTreeModel::addTestTreeItems(const QList<TestTreeItem> &itemList, TestTreeModel::Type type)
{
TestTreeItem *parent = rootItemForType(type);
QModelIndex index = rootIndexForType(type);
beginInsertRows(index, parent->childCount(), parent->childCount() + itemList.size() - 1);
foreach (const TestTreeItem &item, itemList) {
TestTreeItem *toBeAdded = new TestTreeItem(item);
toBeAdded->setParent(parent);
parent->appendChild(toBeAdded);
}
endInsertRows();
emit testTreeModelChanged();
}
void TestTreeModel::updateUnnamedQuickTest(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions)
{
removeUnnamedQuickTests(fileName);
TestTreeItem unnamed = hasUnnamedQuickTests()
? TestTreeItem(*unnamedQuickTests())
: TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS, rootItemForType(QuickTest));
foreach (const QString &functionName, functions.keys()) {
const TestCodeLocationAndType locationAndType = functions.value(functionName);
TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_fileName,
locationAndType.m_type, &unnamed);
testFunction->setLine(locationAndType.m_line);
testFunction->setColumn(locationAndType.m_column);
testFunction->setMainFile(mainFile);
unnamed.appendChild(testFunction);
}
if (hasUnnamedQuickTests())
modifyTestTreeItem(unnamed, QuickTest, QString());
else
addTestTreeItem(unnamed, QuickTest);
}
void TestTreeModel::modifyTestTreeItem(TestTreeItem item, TestTreeModel::Type type, const QString &file)
{
QModelIndex index = rootIndexForType(type);
TestTreeItem *parent = rootItemForType(type);
item.setParent(parent);
if (file.isEmpty()) {
if (TestTreeItem *unnamed = unnamedQuickTests()) {
index = index.child(unnamed->row(), 0);
modifyTestSubtree(index, item);
}
} else {
for (int row = 0; row < parent->childCount(); ++row) {
if (parent->child(row)->filePath() == file) {
index = index.child(row, 0);
modifyTestSubtree(index, item);
break;
}
}
}
}
void TestTreeModel::removeAllTestItems()
{
beginResetModel();
m_autoTestRootItem->removeChildren();
m_quickTestRootItem->removeChildren();
endResetModel();
emit testTreeModelChanged();
}
void TestTreeModel::removeTestTreeItems(const QString &filePath, Type type)
{
bool removed = false;
const QModelIndex rootIndex = rootIndexForType(type);
const int count = rowCount(rootIndex);
for (int row = count - 1; row >= 0; --row) {
const QModelIndex childIndex = rootIndex.child(row, 0);
TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer());
if (filePath == childItem->filePath())
removed |= removeRow(row, rootIndex);
}
if (removed)
emit testTreeModelChanged();
}
TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type)
{
switch (type) {
case AutoTest:
return m_autoTestRootItem;
case QuickTest:
return m_quickTestRootItem;
}
QTC_ASSERT(false, return 0);
}
QModelIndex TestTreeModel::rootIndexForType(TestTreeModel::Type type)
{
switch (type) {
case AutoTest:
return index(0, 0);
case QuickTest:
return index(1, 0);
}
QTC_ASSERT(false, return QModelIndex());
}
void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, const TestTreeItem &newItem)
{
if (!toBeModifiedIndex.isValid())
return;
TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(toBeModifiedIndex.internalPointer());
if (toBeModifiedItem->modifyContent(&newItem))
emit dataChanged(toBeModifiedIndex, toBeModifiedIndex,
QVector<int>() << Qt::DisplayRole << Qt::ToolTipRole << LinkRole);
// process sub-items as well...
const int childCount = toBeModifiedItem->childCount();
const int newChildCount = newItem.childCount();
// for keeping the CheckState on modifications
// TODO might still fail for duplicate entries
QHash<QString, Qt::CheckState> checkStates;
for (int row = 0; row < childCount; ++row) {
const TestTreeItem *child = toBeModifiedItem->child(row);
checkStates.insert(child->name(), child->checked());
}
if (childCount <= newChildCount) {
processChildren(toBeModifiedIndex, newItem, childCount, checkStates);
// add additional items
for (int row = childCount; row < newChildCount; ++row) {
TestTreeItem *newChild = newItem.child(row);
TestTreeItem *toBeAdded = new TestTreeItem(*newChild);
toBeAdded->setParent(toBeModifiedItem);
if (checkStates.contains(toBeAdded->name())
&& checkStates.value(toBeAdded->name()) != Qt::Checked)
toBeAdded->setChecked(checkStates.value(toBeAdded->name()));
beginInsertRows(toBeModifiedIndex, row, row);
toBeModifiedItem->appendChild(toBeAdded);
endInsertRows();
}
} else {
processChildren(toBeModifiedIndex, newItem, newChildCount, checkStates);
// remove rest of the items
removeRows(newChildCount, childCount - newChildCount, toBeModifiedIndex);
}
emit testTreeModelChanged();
}
void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem &newItem,
const int upperBound,
const QHash<QString, Qt::CheckState> &checkStates)
{
static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole
<< Qt::ToolTipRole << LinkRole;
TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(parentIndex.internalPointer());
for (int row = 0; row < upperBound; ++row) {
QModelIndex child = parentIndex.child(row, 0);
TestTreeItem *toBeModifiedChild = toBeModifiedItem->child(row);
TestTreeItem *modifiedChild = newItem.child(row);
if (toBeModifiedChild->modifyContent(modifiedChild))
emit dataChanged(child, child, modificationRoles);
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);
}
}
}
#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;
}
#endif
/***************************** 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());
if (leftItem->type() == TestTreeItem::ROOT)
return left.row() > right.row();
const QString leftVal = m_sourceModel->data(left).toString();
const QString rightVal = m_sourceModel->data(right).toString();
// 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:
if (leftVal == rightVal)
return left.row() > right.row();
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;
const TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
switch (item->type()) {
case TestTreeItem::TEST_DATAFUNCTION:
return m_filterMode & ShowTestData;
case TestTreeItem::TEST_SPECIALFUNCTION:
return m_filterMode & ShowInitAndCleanup;
default:
return true;
}
}
} // namespace Internal
} // namespace Autotest