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

656 lines
23 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 "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_autoTestRootItem(new AutoTestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::Root)),
m_quickTestRootItem(new QuickTestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::Root)),
m_googleTestRootItem(new GoogleTestTreeItem(tr("Google Tests"), QString(), TestTreeItem::Root)),
m_parser(new TestCodeParser(this)),
m_connectionsInitialized(false)
2014-10-07 12:30:54 +02:00
{
rootItem()->appendChild(m_autoTestRootItem);
rootItem()->appendChild(m_quickTestRootItem);
rootItem()->appendChild(m_googleTestRootItem);
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);
2014-10-07 12:30:54 +02:00
}
static TestTreeModel *m_instance = 0;
TestTreeModel *TestTreeModel::instance()
{
if (!m_instance)
m_instance = new TestTreeModel;
return m_instance;
}
TestTreeModel::~TestTreeModel()
{
m_instance = 0;
}
void TestTreeModel::enableParsing()
{
m_refCounter.ref();
setupParsingConnections();
}
void TestTreeModel::enableParsingFromSettings()
{
setupParsingConnections();
}
void TestTreeModel::setupParsingConnections()
{
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,
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);
m_connectionsInitialized = true;
}
void TestTreeModel::disableParsing()
{
if (!m_refCounter.deref() && !AutotestPlugin::instance()->settings()->alwaysParse)
m_parser->setState(TestCodeParser::Disabled);
}
void TestTreeModel::disableParsingFromSettings()
{
if (!m_refCounter.load())
m_parser->setState(TestCodeParser::Disabled);
}
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) {
switch (item->type()) {
case TestTreeItem::TestCase:
if (item->childCount() > 0)
2014-10-07 12:30:54 +02:00
emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
break;
case TestTreeItem::TestFunctionOrSet:
2014-10-07 12:30:54 +02:00
emit dataChanged(index.parent(), index.parent());
break;
default: // avoid warning regarding unhandled enum member
break;
}
}
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));
2014-10-07 12:30:54 +02:00
switch(item->type()) {
case TestTreeItem::TestCase:
2014-11-06 16:01:06 +01:00
if (item->name().isEmpty())
2014-11-13 12:31:58 +01:00
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
2014-10-07 12:30:54 +02:00
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
case TestTreeItem::TestFunctionOrSet:
if (item->parentItem()->name().isEmpty())
2014-11-13 12:31:58 +01:00
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
2014-10-07 12:30:54 +02:00
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
case TestTreeItem::Root:
2014-10-07 12:30:54 +02:00
return Qt::ItemIsEnabled;
case TestTreeItem::TestDataFunction:
case TestTreeItem::TestSpecialFunction:
case TestTreeItem::TestDataTag:
2014-11-13 12:31:58 +01:00
default:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
2014-10-07 12:30:54 +02:00
}
}
bool TestTreeModel::hasTests() const
{
return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0
|| m_googleTestRootItem->childCount() > 0;
2014-10-07 12:30:54 +02:00
}
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
QList<TestConfiguration *> result;
result.append(m_autoTestRootItem->getAllTestConfigurations());
result.append(m_quickTestRootItem->getAllTestConfigurations());
result.append(m_googleTestRootItem->getAllTestConfigurations());
return result;
}
QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
{
QList<TestConfiguration *> result;
result.append(m_autoTestRootItem->getSelectedTestConfigurations());
result.append(m_quickTestRootItem->getSelectedTestConfigurations());
result.append(m_googleTestRootItem->getSelectedTestConfigurations());
return result;
}
TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item) const
{
QTC_ASSERT(item != 0, return 0);
return item->testConfiguration();
}
bool TestTreeModel::hasUnnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row)
if (m_quickTestRootItem->childItem(row)->name().isEmpty())
return true;
return false;
}
2014-11-06 16:01:06 +01:00
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->childItem(row);
2014-11-06 16:01:06 +01:00
if (child->name().isEmpty())
return child;
}
return 0;
}
void TestTreeModel::removeFiles(const QStringList &files)
2014-10-07 12:30:54 +02:00
{
foreach (const QString &file, files)
markForRemoval(file);
sweep();
}
void TestTreeModel::markAllForRemoval()
{
foreach (Utils::TreeItem *item, m_autoTestRootItem->children())
static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);
foreach (Utils::TreeItem *item, m_quickTestRootItem->children())
static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);
foreach (Utils::TreeItem *item, m_googleTestRootItem->children())
static_cast<TestTreeItem *>(item)->markForRemovalRecursively(true);
}
void TestTreeModel::markForRemoval(const QString &filePath)
{
if (filePath.isEmpty())
return;
Type types[] = { AutoTest, QuickTest, GoogleTest };
for (Type type : types) {
TestTreeItem *root = rootItemForType(type);
for (int childRow = root->childCount() - 1; childRow >= 0; --childRow) {
TestTreeItem *child = root->childItem(childRow);
// Qt + named Quick Tests
if (child->filePath() == filePath) {
child->markForRemovalRecursively(true);
} else {
// unnamed Quick Tests and GTest and Qt Tests with separated source/header
int grandChildRow = child->childCount() - 1;
for ( ; grandChildRow >= 0; --grandChildRow) {
TestTreeItem *grandChild = child->childItem(grandChildRow);
if (grandChild->filePath() == filePath) {
grandChild->markForRemovalRecursively(true);
}
}
}
}
}
}
void TestTreeModel::sweep()
{
Type types[] = { AutoTest, QuickTest, GoogleTest };
for (Type type : types) {
TestTreeItem *root = rootItemForType(type);
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
}
QHash<QString, QString> TestTreeModel::testCaseNamesForFiles(QStringList files)
{
QHash<QString, QString> result;
if (!m_autoTestRootItem)
return result;
for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
const TestTreeItem *child = m_autoTestRootItem->childItem(row);
if (files.contains(child->filePath())) {
result.insert(child->filePath(), child->name());
}
for (int childRow = 0, children = child->childCount(); childRow < children; ++childRow) {
const TestTreeItem *grandChild = child->childItem(childRow);
if (files.contains(grandChild->filePath()))
result.insert(grandChild->filePath(), child->name());
}
}
return result;
}
/**
* @note after calling this function emit testTreeModelChanged() if it returns true
*/
bool TestTreeModel::sweepChildren(TestTreeItem *item)
{
bool hasChanged = false;
for (int row = item->childCount() - 1; row >= 0; --row) {
TestTreeItem *child = item->childItem(row);
if (child->parentItem()->type() != TestTreeItem::Root && child->markedForRemoval()) {
delete takeItem(child);
hasChanged = true;
continue;
}
if (bool noEndNode = child->hasChildren()) {
hasChanged |= sweepChildren(child);
if (noEndNode && child->childCount() == 0) {
delete takeItem(child);
hasChanged = true;
continue;
}
}
hasChanged |= child->newlyAdded();
child->markForRemoval(false);
}
return hasChanged;
}
void TestTreeModel::onParseResultReady(const TestParseResult &result)
{
switch (result.type) {
case AutoTest:
handleParseResult(result);
break;
case QuickTest:
if (result.testCaseName.isEmpty()) {
handleUnnamedQuickParseResult(result);
break;
}
handleParseResult(result);
break;
case GoogleTest:
QTC_ASSERT(result.dataTagsOrTestSets.size() == 1, return);
handleGTestParseResult(result);
break;
case Invalid:
QTC_ASSERT(false, qWarning("TestParseResult of type Invalid unexpected."));
break;
}
}
void TestTreeModel::handleParseResult(const TestParseResult &result)
{
TestTreeItem *root;
switch (result.type) {
case AutoTest:
root = m_autoTestRootItem;
break;
case QuickTest:
root = m_quickTestRootItem;
break;
default:
QTC_ASSERT(false, return); // should never happen, just to avoid warning
}
TestTreeItem *toBeModified = root->findChildByFile(result.fileName);
// if there's no matching item, add the new one
if (!toBeModified) {
if (result.type == AutoTest)
root->appendChild(AutoTestTreeItem::createTestItem(result));
else
root->appendChild(QuickTestTreeItem::createTestItem(result));
return;
}
// else we have to check level by level.. first the current level...
bool changed = toBeModified->modifyTestCaseContent(result.testCaseName, result.line,
result.column);
toBeModified->markForRemoval(false);
if (changed)
emit dataChanged(indexForItem(toBeModified), indexForItem(toBeModified));
// ...now the functions
foreach (const QString &func, result.functions.keys()) {
TestTreeItem *functionItem = toBeModified->findChildByName(func);
// if there's no function matching, add the new one
if (!functionItem) {
const QString qualifiedName = result.testCaseName + QLatin1String("::") + func;
if (result.type == AutoTest) {
toBeModified->appendChild(AutoTestTreeItem::createFunctionItem(
func, result.functions.value(func),
result.dataTagsOrTestSets.value(qualifiedName)));
} else {
toBeModified->appendChild(QuickTestTreeItem::createFunctionItem(
func, result.functions.value(func)));
}
continue;
}
// else we have to check level by level.. first the current level...
changed = functionItem->modifyTestFunctionContent(result.functions.value(func));
functionItem->markForRemoval(false);
if (changed)
emit dataChanged(indexForItem(functionItem), indexForItem(functionItem));
// ...now the data tags - actually these are supported only for AutoTestTreeItem
const QString &funcFileName = result.functions.value(func).m_name;
const QString qualifiedFunctionName = result.testCaseName + QLatin1String("::") + func;
foreach (const TestCodeLocationAndType &location, result.dataTagsOrTestSets.value(qualifiedFunctionName)) {
TestTreeItem *dataTagItem = functionItem->findChildByName(location.m_name);
if (!dataTagItem) {
functionItem->appendChild(AutoTestTreeItem::createDataTagItem(funcFileName, location));
continue;
}
changed = dataTagItem->modifyDataTagContent(funcFileName, location);
dataTagItem->markForRemoval(false);
if (changed)
emit dataChanged(indexForItem(dataTagItem), indexForItem(dataTagItem));
}
}
}
void TestTreeModel::handleUnnamedQuickParseResult(const TestParseResult &result)
{
TestTreeItem *toBeModified = unnamedQuickTests();
if (!toBeModified) {
m_quickTestRootItem->appendChild(QuickTestTreeItem::createUnnamedQuickTestItem(result));
return;
}
// if we have already Unnamed Quick tests we might update them..
foreach (const QString &func, result.functions.keys()) {
const TestCodeLocationAndType &location = result.functions.value(func);
TestTreeItem *functionItem = toBeModified->findChildByNameAndFile(func, location.m_name);
if (!functionItem) {
toBeModified->appendChild(QuickTestTreeItem::createUnnamedQuickFunctionItem(
func, result));
continue;
}
functionItem->modifyLineAndColumn(location);
functionItem->markForRemoval(false);
}
}
void TestTreeModel::handleGTestParseResult(const TestParseResult &result)
{
GoogleTestTreeItem::TestStates states = GoogleTestTreeItem::Enabled;
if (result.parameterized)
states |= GoogleTestTreeItem::Parameterized;
if (result.typed)
states |= GoogleTestTreeItem::Typed;
TestTreeItem *toBeModified = m_googleTestRootItem->findChildByNameStateAndFile(
result.testCaseName, states, result.proFile);
if (!toBeModified) {
m_googleTestRootItem->appendChild(GoogleTestTreeItem::createTestItem(result));
return;
}
// if found nothing has to be updated as all relevant members are used to find the item
foreach (const TestCodeLocationAndType &location , result.dataTagsOrTestSets.first()) {
TestTreeItem *testSetItem = toBeModified->findChildByNameAndFile(location.m_name,
result.fileName);
if (!testSetItem) {
toBeModified->appendChild(GoogleTestTreeItem::createTestSetItem(result, location));
continue;
}
bool changed = static_cast<GoogleTestTreeItem *>(testSetItem)->modifyTestSetContent(
result.fileName, location);
testSetItem->markForRemoval(false);
if (changed)
emit dataChanged(indexForItem(testSetItem), indexForItem(testSetItem));
}
}
void TestTreeModel::removeAllTestItems()
2014-11-06 16:01:06 +01:00
{
m_autoTestRootItem->removeChildren();
2014-11-06 16:01:06 +01:00
m_quickTestRootItem->removeChildren();
m_googleTestRootItem->removeChildren();
2014-11-06 16:01:06 +01:00
emit testTreeModelChanged();
}
TestTreeItem *TestTreeModel::rootItemForType(TestTreeModel::Type type)
{
switch (type) {
case AutoTest:
return m_autoTestRootItem;
case QuickTest:
return m_quickTestRootItem;
case GoogleTest:
return m_googleTestRootItem;
case Invalid:
break;
}
QTC_ASSERT(false, return 0);
}
#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;
}
int TestTreeModel::dataTagsCount() const
{
int dataTagCount = 0;
foreach (Utils::TreeItem *item, m_autoTestRootItem->children()) {
TestTreeItem *classItem = static_cast<TestTreeItem *>(item);
foreach (Utils::TreeItem *functionItem, classItem->children())
dataTagCount += functionItem->childCount();
}
return dataTagCount;
}
int TestTreeModel::gtestNamesCount() const
{
return m_googleTestRootItem ? m_googleTestRootItem->childCount() : 0;
}
QMultiMap<QString, int> TestTreeModel::gtestNamesAndSets() const
{
QMultiMap<QString, int> result;
if (m_googleTestRootItem) {
for (int row = 0, count = m_googleTestRootItem->childCount(); row < count; ++row) {
const TestTreeItem *current = m_googleTestRootItem->childItem(row);
result.insert(current->name(), current->childCount());
}
}
return result;
}
#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, Qt::DisplayRole).toString();
const QString rightVal = m_sourceModel->data(right, Qt::DisplayRole).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:
2014-11-13 12:31:58 +01:00
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;
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