AutoTest: Allow grouping of test cases

Grouping of test cases can now get enabled for each
registered framework.
For now grouping happens only folder based.

Task-number: QTCREATORBUG-17979
Change-Id: Ic0e5c0ecc76998a1aedea8aa0845f6d9b53fb179
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Stenger
2017-12-06 08:45:36 +01:00
parent b3e24fbfd4
commit 4eabcda3a1
18 changed files with 253 additions and 23 deletions

View File

@@ -26,6 +26,7 @@
#include "gtesttreeitem.h" #include "gtesttreeitem.h"
#include "gtestconfiguration.h" #include "gtestconfiguration.h"
#include "gtestparser.h" #include "gtestparser.h"
#include "../testframeworkmanager.h"
#include <cpptools/cppmodelmanager.h> #include <cpptools/cppmodelmanager.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
@@ -59,6 +60,7 @@ QVariant GTestTreeItem::data(int column, int role) const
case Qt::CheckStateRole: case Qt::CheckStateRole:
switch (type()) { switch (type()) {
case Root: case Root:
case GroupNode:
case TestCase: case TestCase:
case TestFunctionOrSet: case TestFunctionOrSet:
return checked(); return checked();
@@ -237,6 +239,22 @@ TestTreeItem *GTestTreeItem::find(const TestParseResult *result)
states |= GTestTreeItem::Typed; states |= GTestTreeItem::Typed;
switch (type()) { switch (type()) {
case Root: case Root:
if (TestFrameworkManager::instance()->groupingEnabled(result->frameworkId)) {
const QFileInfo fileInfo(parseResult->fileName);
const QFileInfo base(fileInfo.absolutePath());
for (int row = 0; row < childCount(); ++row) {
GTestTreeItem *group = static_cast<GTestTreeItem *>(childAt(row));
if (group->filePath() != base.absoluteFilePath())
continue;
if (auto groupChild = group->findChildByNameStateAndFile(parseResult->name, states,
parseResult->proFile)) {
return groupChild;
}
}
return nullptr;
}
return findChildByNameStateAndFile(parseResult->name, states, parseResult->proFile);
case GroupNode:
return findChildByNameStateAndFile(parseResult->name, states, parseResult->proFile); return findChildByNameStateAndFile(parseResult->name, states, parseResult->proFile);
case TestCase: case TestCase:
return findChildByNameAndFile(result->name, result->fileName); return findChildByNameAndFile(result->name, result->fileName);
@@ -257,6 +275,13 @@ bool GTestTreeItem::modify(const TestParseResult *result)
} }
} }
TestTreeItem *GTestTreeItem::createParentGroupNode() const
{
const QFileInfo fileInfo(filePath());
const QFileInfo base(fileInfo.absolutePath());
return new GTestTreeItem(base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode);
}
bool GTestTreeItem::modifyTestSetContent(const GTestParseResult *result) bool GTestTreeItem::modifyTestSetContent(const GTestParseResult *result)
{ {
bool hasBeenModified = modifyLineAndColumn(result); bool hasBeenModified = modifyLineAndColumn(result);

View File

@@ -58,6 +58,7 @@ public:
QList<TestConfiguration *> getSelectedTestConfigurations() const override; QList<TestConfiguration *> getSelectedTestConfigurations() const override;
TestTreeItem *find(const TestParseResult *result) override; TestTreeItem *find(const TestParseResult *result) override;
bool modify(const TestParseResult *result) override; bool modify(const TestParseResult *result) override;
TestTreeItem *createParentGroupNode() const override;
void setStates(TestStates states) { m_state = states; } void setStates(TestStates states) { m_state = states; }
void setState(TestState state) { m_state |= state; } void setState(TestState state) { m_state |= state; }

View File

@@ -69,6 +69,8 @@ public:
bool active() const { return m_active; } bool active() const { return m_active; }
void setActive(bool active) { m_active = active; } void setActive(bool active) { m_active = active; }
bool grouping() const { return m_grouping; }
void setGrouping(bool group) { m_grouping = group; }
protected: protected:
virtual ITestParser *createTestParser() const = 0; virtual ITestParser *createTestParser() const = 0;
@@ -78,6 +80,7 @@ private:
TestTreeItem *m_rootNode = 0; TestTreeItem *m_rootNode = 0;
ITestParser *m_testParser = 0; ITestParser *m_testParser = 0;
bool m_active = false; bool m_active = false;
bool m_grouping = false;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -26,6 +26,7 @@
#include "qttesttreeitem.h" #include "qttesttreeitem.h"
#include "qttestconfiguration.h" #include "qttestconfiguration.h"
#include "qttestparser.h" #include "qttestparser.h"
#include "../testframeworkmanager.h"
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -227,6 +228,19 @@ TestTreeItem *QtTestTreeItem::find(const TestParseResult *result)
switch (type()) { switch (type()) {
case Root: case Root:
if (TestFrameworkManager::instance()->groupingEnabled(result->frameworkId)) {
const QString path = QFileInfo(result->fileName).absolutePath();
for (int row = 0; row < childCount(); ++row) {
TestTreeItem *group = childItem(row);
if (group->filePath() != path)
continue;
if (auto groupChild = group->findChildByFile(result->fileName))
return groupChild;
}
return nullptr;
}
return findChildByFile(result->fileName);
case GroupNode:
return findChildByFile(result->fileName); return findChildByFile(result->fileName);
case TestCase: { case TestCase: {
const QtTestParseResult *qtResult = static_cast<const QtTestParseResult *>(result); const QtTestParseResult *qtResult = static_cast<const QtTestParseResult *>(result);
@@ -259,6 +273,13 @@ bool QtTestTreeItem::modify(const TestParseResult *result)
} }
} }
TestTreeItem *QtTestTreeItem::createParentGroupNode() const
{
const QFileInfo fileInfo(filePath());
const QFileInfo base(fileInfo.absolutePath());
return new QtTestTreeItem(base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode);
}
TestTreeItem *QtTestTreeItem::findChildByNameAndInheritance(const QString &name, bool inherited) const TestTreeItem *QtTestTreeItem::findChildByNameAndInheritance(const QString &name, bool inherited) const
{ {
return findChildBy([name, inherited](const TestTreeItem *other) -> bool { return findChildBy([name, inherited](const TestTreeItem *other) -> bool {

View File

@@ -48,6 +48,7 @@ public:
bool modify(const TestParseResult *result) override; bool modify(const TestParseResult *result) override;
void setInherited(bool inherited) { m_inherited = inherited; } void setInherited(bool inherited) { m_inherited = inherited; }
bool inherited() const { return m_inherited; } bool inherited() const { return m_inherited; }
TestTreeItem *createParentGroupNode() const override;
private: private:
TestTreeItem *findChildByNameAndInheritance(const QString &name, bool inherited) const; TestTreeItem *findChildByNameAndInheritance(const QString &name, bool inherited) const;
QString nameSuffix() const; QString nameSuffix() const;

View File

@@ -26,6 +26,7 @@
#include "quicktesttreeitem.h" #include "quicktesttreeitem.h"
#include "quicktestconfiguration.h" #include "quicktestconfiguration.h"
#include "quicktestparser.h" #include "quicktestparser.h"
#include "../testframeworkmanager.h"
#include <cpptools/cppmodelmanager.h> #include <cpptools/cppmodelmanager.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
@@ -267,7 +268,22 @@ TestTreeItem *QuickTestTreeItem::find(const TestParseResult *result)
switch (type()) { switch (type()) {
case Root: case Root:
return result->name.isEmpty() ? unnamedQuickTests() : findChildByFile(result->fileName); if (result->name.isEmpty())
return unnamedQuickTests();
if (TestFrameworkManager::instance()->groupingEnabled(result->frameworkId)) {
const QString path = QFileInfo(result->fileName).absolutePath();
for (int row = 0; row < childCount(); ++row) {
TestTreeItem *group = childItem(row);
if (group->filePath() != path)
continue;
if (auto groupChild = group->findChildByFile(result->fileName))
return groupChild;
}
return nullptr;
}
return findChildByFile(result->fileName);
case GroupNode:
return findChildByFile(result->fileName);
case TestCase: case TestCase:
return name().isEmpty() ? findChildByNameAndFile(result->name, result->fileName) return name().isEmpty() ? findChildByNameAndFile(result->name, result->fileName)
: findChildByName(result->name); : findChildByName(result->name);
@@ -303,6 +319,26 @@ bool QuickTestTreeItem::lessThan(const TestTreeItem *other, TestTreeItem::SortMo
return TestTreeItem::lessThan(other, mode); return TestTreeItem::lessThan(other, mode);
} }
bool QuickTestTreeItem::isGroupNodeFor(const TestTreeItem *other) const
{
QTC_ASSERT(other, return false);
if (other->name().isEmpty()) // unnamed quick tests will not get grouped
return false;
return TestTreeItem::isGroupNodeFor(other);
}
TestTreeItem *QuickTestTreeItem::createParentGroupNode() const
{
if (filePath().isEmpty() || name().isEmpty())
return nullptr;
if (type() == TestFunctionOrSet)
return nullptr;
const QFileInfo fileInfo(filePath());
const QFileInfo base(fileInfo.absolutePath());
return new QuickTestTreeItem(base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode);
}
QSet<QString> QuickTestTreeItem::internalTargets() const QSet<QString> QuickTestTreeItem::internalTargets() const
{ {
QSet<QString> result; QSet<QString> result;

View File

@@ -47,6 +47,8 @@ public:
TestTreeItem *find(const TestParseResult *result) override; TestTreeItem *find(const TestParseResult *result) override;
bool modify(const TestParseResult *result) override; bool modify(const TestParseResult *result) override;
bool lessThan(const TestTreeItem *other, SortMode mode) const override; bool lessThan(const TestTreeItem *other, SortMode mode) const override;
bool isGroupNodeFor(const TestTreeItem *other) const override;
TestTreeItem *createParentGroupNode() const override;
QSet<QString> internalTargets() const override; QSet<QString> internalTargets() const override;
private: private:
TestTreeItem *unnamedQuickTests() const; TestTreeItem *unnamedQuickTests() const;

View File

@@ -94,8 +94,10 @@ void TestFrameworkManager::activateFrameworksFromSettings(QSharedPointer<TestSet
{ {
FrameworkIterator it = m_registeredFrameworks.begin(); FrameworkIterator it = m_registeredFrameworks.begin();
FrameworkIterator end = m_registeredFrameworks.end(); FrameworkIterator end = m_registeredFrameworks.end();
for ( ; it != end; ++it) for ( ; it != end; ++it) {
it.value()->setActive(settings->frameworks.value(it.key(), false)); it.value()->setActive(settings->frameworks.value(it.key(), false));
it.value()->setGrouping(settings->frameworksGrouping.value(it.key(), false));
}
} }
QString TestFrameworkManager::frameworkNameForId(const Core::Id &id) const QString TestFrameworkManager::frameworkNameForId(const Core::Id &id) const
@@ -181,6 +183,18 @@ bool TestFrameworkManager::isActive(const Core::Id &frameworkId) const
return framework ? framework->active() : false; return framework ? framework->active() : false;
} }
bool TestFrameworkManager::groupingEnabled(const Core::Id &frameworkId) const
{
ITestFramework *framework = m_registeredFrameworks.value(frameworkId);
return framework ? framework->grouping() : false;
}
void TestFrameworkManager::setGroupingEnabledFor(const Core::Id &frameworkId, bool enabled)
{
if (ITestFramework *framework = m_registeredFrameworks.value(frameworkId))
framework->setGrouping(enabled);
}
bool TestFrameworkManager::hasActiveFrameworks() const bool TestFrameworkManager::hasActiveFrameworks() const
{ {
for (ITestFramework *framework : m_registeredFrameworks.values()) { for (ITestFramework *framework : m_registeredFrameworks.values()) {

View File

@@ -63,6 +63,8 @@ public:
QSharedPointer<IFrameworkSettings> settingsForTestFramework(const Core::Id &frameworkId) const; QSharedPointer<IFrameworkSettings> settingsForTestFramework(const Core::Id &frameworkId) const;
void synchronizeSettings(QSettings *s); void synchronizeSettings(QSettings *s);
bool isActive(const Core::Id &frameworkId) const; bool isActive(const Core::Id &frameworkId) const;
bool groupingEnabled(const Core::Id &frameworkId) const;
void setGroupingEnabledFor(const Core::Id &frameworkId, bool enabled);
bool hasActiveFrameworks() const; bool hasActiveFrameworks() const;
private: private:

View File

@@ -42,6 +42,7 @@ static const char autoScrollKey[] = "AutoScrollResults";
static const char filterScanKey[] = "FilterScan"; static const char filterScanKey[] = "FilterScan";
static const char filtersKey[] = "WhiteListFilters"; static const char filtersKey[] = "WhiteListFilters";
static const char processArgsKey[] = "ProcessArgs"; static const char processArgsKey[] = "ProcessArgs";
static const char groupSuffix[] = ".group";
static const int defaultTimeout = 60000; static const int defaultTimeout = 60000;
@@ -61,9 +62,11 @@ void TestSettings::toSettings(QSettings *s) const
s->setValue(processArgsKey, processArgs); s->setValue(processArgsKey, processArgs);
s->setValue(filterScanKey, filterScan); s->setValue(filterScanKey, filterScan);
s->setValue(filtersKey, whiteListFilters); s->setValue(filtersKey, whiteListFilters);
// store frameworks and their current active state // store frameworks and their current active and grouping state
for (const Core::Id &id : frameworks.keys()) for (const Core::Id &id : frameworks.keys()) {
s->setValue(QLatin1String(id.name()), frameworks.value(id)); s->setValue(QLatin1String(id.name()), frameworks.value(id));
s->setValue(QLatin1String(id.name().append(groupSuffix)), frameworksGrouping.value(id));
}
s->endGroup(); s->endGroup();
} }
@@ -82,9 +85,14 @@ void TestSettings::fromSettings(QSettings *s)
TestFrameworkManager *frameworkManager = TestFrameworkManager::instance(); TestFrameworkManager *frameworkManager = TestFrameworkManager::instance();
const QList<Core::Id> &registered = frameworkManager->registeredFrameworkIds(); const QList<Core::Id> &registered = frameworkManager->registeredFrameworkIds();
frameworks.clear(); frameworks.clear();
frameworksGrouping.clear();
for (const Core::Id &id : registered) { for (const Core::Id &id : registered) {
// get their active state
frameworks.insert(id, s->value(QLatin1String(id.name()), frameworks.insert(id, s->value(QLatin1String(id.name()),
frameworkManager->isActive(id)).toBool()); frameworkManager->isActive(id)).toBool());
// and whether grouping is enabled
frameworksGrouping.insert(id, s->value(QLatin1String(id.name().append(groupSuffix)),
frameworkManager->groupingEnabled(id)).toBool());
} }
s->endGroup(); s->endGroup();
} }

View File

@@ -50,6 +50,7 @@ struct TestSettings
bool filterScan = false; bool filterScan = false;
bool processArgs = false; bool processArgs = false;
QHash<Core::Id, bool> frameworks; QHash<Core::Id, bool> frameworks;
QHash<Core::Id, bool> frameworksGrouping;
QStringList whiteListFilters; QStringList whiteListFilters;
}; };

View File

@@ -164,7 +164,7 @@ TestSettings TestSettingsWidget::settings() const
result.autoScroll = m_ui.autoScrollCB->isChecked(); result.autoScroll = m_ui.autoScrollCB->isChecked();
result.processArgs = m_ui.processArgsCB->isChecked(); result.processArgs = m_ui.processArgsCB->isChecked();
result.filterScan = m_ui.filterGroupBox->isChecked(); result.filterScan = m_ui.filterGroupBox->isChecked();
result.frameworks = frameworks(); frameworkSettings(result);
result.whiteListFilters = filters(); result.whiteListFilters = filters();
return result; return result;
} }
@@ -180,6 +180,11 @@ void TestSettingsWidget::populateFrameworksListWidget(const QHash<Core::Id, bool
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
item->setCheckState(0, frameworks.value(id) ? Qt::Checked : Qt::Unchecked); item->setCheckState(0, frameworks.value(id) ? Qt::Checked : Qt::Unchecked);
item->setData(0, Qt::UserRole, id.toSetting()); item->setData(0, Qt::UserRole, id.toSetting());
item->setData(1, Qt::CheckStateRole, frameworkManager->groupingEnabled(id) ? Qt::Checked
: Qt::Unchecked);
item->setToolTip(0, tr("Enable or disable test frameworks to be handled by the AutoTest "
"plugin."));
item->setToolTip(1, tr("Enable or disable grouping of test cases by folder."));
} }
} }
@@ -189,18 +194,18 @@ void TestSettingsWidget::populateFiltersWidget(const QStringList &filters)
new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} ); new QTreeWidgetItem(m_ui.filterTreeWidget, {filter} );
} }
QHash<Core::Id, bool> TestSettingsWidget::frameworks() const void TestSettingsWidget::frameworkSettings(TestSettings &settings) const
{ {
QHash<Core::Id, bool> frameworks;
const QAbstractItemModel *model = m_ui.frameworkTreeWidget->model(); const QAbstractItemModel *model = m_ui.frameworkTreeWidget->model();
QTC_ASSERT(model, return frameworks); QTC_ASSERT(model, return);
const int itemCount = model->rowCount(); const int itemCount = model->rowCount();
for (int row = 0; row < itemCount; ++row) { for (int row = 0; row < itemCount; ++row) {
const QModelIndex index = model->index(row, 0); QModelIndex idx = model->index(row, 0);
frameworks.insert(Core::Id::fromSetting(index.data(Qt::UserRole)), const Core::Id id = Core::Id::fromSetting(idx.data(Qt::UserRole));
index.data(Qt::CheckStateRole) == Qt::Checked); settings.frameworks.insert(id, idx.data(Qt::CheckStateRole) == Qt::Checked);
idx = model->index(row, 1);
settings.frameworksGrouping.insert(id, idx.data(Qt::CheckStateRole) == Qt::Checked);
} }
return frameworks;
} }
QStringList TestSettingsWidget::filters() const QStringList TestSettingsWidget::filters() const
@@ -298,13 +303,20 @@ void TestSettingsPage::apply()
bool frameworkSyncNecessary = newSettings.frameworks != m_settings->frameworks; bool frameworkSyncNecessary = newSettings.frameworks != m_settings->frameworks;
bool forceReparse = newSettings.filterScan != m_settings->filterScan || bool forceReparse = newSettings.filterScan != m_settings->filterScan ||
newSettings.whiteListFilters.toSet() != m_settings->whiteListFilters.toSet(); newSettings.whiteListFilters.toSet() != m_settings->whiteListFilters.toSet();
const QList<Core::Id> changedIds = Utils::filtered(newSettings.frameworksGrouping.keys(),
[newSettings, this] (const Core::Id &id) {
return newSettings.frameworksGrouping[id] != m_settings->frameworksGrouping[id];
});
*m_settings = newSettings; *m_settings = newSettings;
m_settings->toSettings(Core::ICore::settings()); m_settings->toSettings(Core::ICore::settings());
TestFrameworkManager::instance()->activateFrameworksFromSettings(m_settings); TestFrameworkManager *frameworkManager = TestFrameworkManager::instance();
frameworkManager->activateFrameworksFromSettings(m_settings);
if (frameworkSyncNecessary) if (frameworkSyncNecessary)
TestTreeModel::instance()->syncTestFrameworks(); TestTreeModel::instance()->syncTestFrameworks();
else if (forceReparse) else if (forceReparse)
TestTreeModel::instance()->parser()->emitUpdateTestTree(); TestTreeModel::instance()->parser()->emitUpdateTestTree();
else if (!changedIds.isEmpty())
TestTreeModel::instance()->rebuild(changedIds);
} }
} // namespace Internal } // namespace Internal

View File

@@ -48,7 +48,7 @@ public:
private: private:
void populateFrameworksListWidget(const QHash<Core::Id, bool> &frameworks); void populateFrameworksListWidget(const QHash<Core::Id, bool> &frameworks);
void populateFiltersWidget(const QStringList &filters); void populateFiltersWidget(const QStringList &filters);
QHash<Core::Id, bool> frameworks() const; void frameworkSettings(TestSettings &settings) const;
QStringList filters() const; QStringList filters() const;
void onFrameworkItemChanged(); void onFrameworkItemChanged();
void onAddFilterClicked(); void onAddFilterClicked();

View File

@@ -183,12 +183,29 @@ Warning: this is an experimental feature and might lead to failing to execute th
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="headerHidden"> <property name="headerHidden">
<bool>true</bool> <bool>false</bool>
</property> </property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>150</number>
</attribute>
<column> <column>
<property name="text"> <property name="text">
<string>Framework</string> <string>Framework</string>
</property> </property>
<property name="toolTip">
<string>Selects the test frameworks to be handled by the AutoTest plugin.</string>
</property>
</column>
<column>
<property name="text">
<string>Group</string>
</property>
<property name="toolTip">
<string>Enables grouping of test cases.</string>
</property>
</column> </column>
</widget> </widget>
</item> </item>

View File

@@ -32,6 +32,7 @@
#include <cpptools/cppmodelmanager.h> #include <cpptools/cppmodelmanager.h>
#include <cpptools/cpptoolsreuse.h> #include <cpptools/cpptoolsreuse.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <utils/utilsicons.h>
#include <QIcon> #include <QIcon>
@@ -43,21 +44,31 @@ TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type ty
m_filePath(filePath), m_filePath(filePath),
m_type(type) m_type(type)
{ {
m_checked = (m_type == TestCase || m_type == TestFunctionOrSet || m_type == Root) switch (m_type) {
? Qt::Checked : Qt::Unchecked; case Root:
case GroupNode:
case TestCase:
case TestFunctionOrSet:
m_checked = Qt::Checked;
break;
default:
m_checked = Qt::Unchecked;
break;
}
} }
static QIcon testTreeIcon(TestTreeItem::Type type) static QIcon testTreeIcon(TestTreeItem::Type type)
{ {
static QIcon icons[] = { static QIcon icons[] = {
QIcon(), QIcon(),
Utils::Icons::OPENFILE.icon(),
CPlusPlus::Icons::iconForType(CPlusPlus::Icons::ClassIconType), CPlusPlus::Icons::iconForType(CPlusPlus::Icons::ClassIconType),
CPlusPlus::Icons::iconForType(CPlusPlus::Icons::SlotPrivateIconType), CPlusPlus::Icons::iconForType(CPlusPlus::Icons::SlotPrivateIconType),
QIcon(":/images/data.png") QIcon(":/images/data.png")
}; };
if (int(type) >= int(sizeof icons / sizeof *icons)) if (int(type) >= int(sizeof icons / sizeof *icons))
return icons[2]; return icons[3];
return icons[type]; return icons[type];
} }
@@ -76,6 +87,8 @@ QVariant TestTreeItem::data(int /*column*/, int role) const
case Qt::CheckStateRole: case Qt::CheckStateRole:
return QVariant(); return QVariant();
case LinkRole: { case LinkRole: {
if (m_type == GroupNode)
return QVariant();
QVariant itemLink; QVariant itemLink;
itemLink.setValue(Utils::Link(m_filePath, m_line, m_column)); itemLink.setValue(Utils::Link(m_filePath, m_line, m_column));
return itemLink; return itemLink;
@@ -105,6 +118,7 @@ Qt::ItemFlags TestTreeItem::flags(int /*column*/) const
static const Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; static const Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
switch (m_type) { switch (m_type) {
case Root: case Root:
case GroupNode:
return Qt::ItemIsEnabled | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable; return Qt::ItemIsEnabled | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable;
case TestCase: case TestCase:
return defaultFlags | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable; return defaultFlags | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable;
@@ -164,6 +178,7 @@ void TestTreeItem::setChecked(const Qt::CheckState checkState)
break; break;
} }
case Root: case Root:
case GroupNode:
case TestFunctionOrSet: case TestFunctionOrSet:
case TestCase: { case TestCase: {
Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked); Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
@@ -185,6 +200,7 @@ Qt::CheckState TestTreeItem::checked() const
{ {
switch (m_type) { switch (m_type) {
case Root: case Root:
case GroupNode:
case TestCase: case TestCase:
case TestFunctionOrSet: case TestFunctionOrSet:
case TestDataTag: case TestDataTag:
@@ -268,6 +284,9 @@ bool TestTreeItem::lessThan(const TestTreeItem *other, SortMode mode) const
return index().row() > other->index().row(); return index().row() > other->index().row();
return lhs > rhs; return lhs > rhs;
case Naturally: { case Naturally: {
if (m_type == GroupNode && other->type() == GroupNode)
return m_filePath > other->filePath();
const Utils::Link &leftLink = data(0, LinkRole).value<Utils::Link>(); const Utils::Link &leftLink = data(0, LinkRole).value<Utils::Link>();
const Utils::Link &rightLink = other->data(0, LinkRole).value<Utils::Link>(); const Utils::Link &rightLink = other->data(0, LinkRole).value<Utils::Link>();
if (leftLink.targetFileName == rightLink.targetFileName) { if (leftLink.targetFileName == rightLink.targetFileName) {
@@ -282,6 +301,16 @@ bool TestTreeItem::lessThan(const TestTreeItem *other, SortMode mode) const
} }
} }
bool TestTreeItem::isGroupNodeFor(const TestTreeItem *other) const
{
QTC_ASSERT(other, return false);
if (type() != TestTreeItem::GroupNode)
return false;
// for now there's only the possibility to have 'Folder' nodes
return QFileInfo(other->filePath()).absolutePath() == filePath();
}
QSet<QString> TestTreeItem::internalTargets() const QSet<QString> TestTreeItem::internalTargets() const
{ {
auto cppMM = CppTools::CppModelManager::instance(); auto cppMM = CppTools::CppModelManager::instance();
@@ -301,7 +330,7 @@ QSet<QString> TestTreeItem::internalTargets() const
void TestTreeItem::revalidateCheckState() void TestTreeItem::revalidateCheckState()
{ {
const Type ttiType = type(); const Type ttiType = type();
if (ttiType != TestCase && ttiType != TestFunctionOrSet && ttiType != Root) if (ttiType != TestCase && ttiType != TestFunctionOrSet && ttiType != Root && ttiType != GroupNode)
return; return;
if (childCount() == 0) // can this happen? (we're calling revalidateCS() on parentItem() if (childCount() == 0) // can this happen? (we're calling revalidateCS() on parentItem()
return; return;
@@ -323,13 +352,13 @@ void TestTreeItem::revalidateCheckState()
foundPartiallyChecked |= (child->checked() == Qt::PartiallyChecked); foundPartiallyChecked |= (child->checked() == Qt::PartiallyChecked);
if (foundPartiallyChecked || (foundChecked && foundUnchecked)) { if (foundPartiallyChecked || (foundChecked && foundUnchecked)) {
m_checked = Qt::PartiallyChecked; m_checked = Qt::PartiallyChecked;
if (ttiType == TestFunctionOrSet || ttiType == TestCase) if (ttiType == TestFunctionOrSet || ttiType == TestCase || ttiType == GroupNode)
parentItem()->revalidateCheckState(); parentItem()->revalidateCheckState();
return; return;
} }
} }
m_checked = (foundUnchecked ? Qt::Unchecked : Qt::Checked); m_checked = (foundUnchecked ? Qt::Unchecked : Qt::Checked);
if (ttiType == TestFunctionOrSet || ttiType == TestCase) if (ttiType == TestFunctionOrSet || ttiType == TestCase || ttiType == GroupNode)
parentItem()->revalidateCheckState(); parentItem()->revalidateCheckState();
} }

View File

@@ -55,6 +55,7 @@ public:
enum Type enum Type
{ {
Root, Root,
GroupNode,
TestCase, TestCase,
TestFunctionOrSet, TestFunctionOrSet,
TestDataTag, TestDataTag,
@@ -112,7 +113,8 @@ public:
virtual bool lessThan(const TestTreeItem *other, SortMode mode) const; virtual bool lessThan(const TestTreeItem *other, SortMode mode) const;
virtual TestTreeItem *find(const TestParseResult *result) = 0; virtual TestTreeItem *find(const TestParseResult *result) = 0;
virtual bool modify(const TestParseResult *result) = 0; virtual bool modify(const TestParseResult *result) = 0;
virtual bool isGroupNodeFor(const TestTreeItem *other) const;
virtual TestTreeItem *createParentGroupNode() const = 0;
virtual QSet<QString> internalTargets() const; virtual QSet<QString> internalTargets() const;
protected: protected:
typedef std::function<bool(const TestTreeItem *)> CompareFunction; typedef std::function<bool(const TestTreeItem *)> CompareFunction;

View File

@@ -110,6 +110,7 @@ bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
switch (item->type()) { switch (item->type()) {
case TestTreeItem::Root: case TestTreeItem::Root:
case TestTreeItem::GroupNode:
case TestTreeItem::TestCase: case TestTreeItem::TestCase:
if (item->childCount() > 0) if (item->childCount() > 0)
emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0)); emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
@@ -174,6 +175,31 @@ void TestTreeModel::syncTestFrameworks()
emit updatedActiveFrameworks(sortedIds.size()); emit updatedActiveFrameworks(sortedIds.size());
} }
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) void TestTreeModel::removeFiles(const QStringList &files)
{ {
for (const QString &file : files) for (const QString &file : files)
@@ -239,6 +265,24 @@ bool TestTreeModel::sweepChildren(TestTreeItem *item)
return hasChanged; 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);
}
void TestTreeModel::onParseResultReady(const TestParseResultPtr result) void TestTreeModel::onParseResultReady(const TestParseResultPtr result)
{ {
TestTreeItem *rootNode TestTreeItem *rootNode
@@ -249,10 +293,19 @@ void TestTreeModel::onParseResultReady(const TestParseResultPtr result)
void TestTreeModel::handleParseResult(const TestParseResult *result, TestTreeItem *parentNode) void TestTreeModel::handleParseResult(const TestParseResult *result, TestTreeItem *parentNode)
{ {
const bool groupingEnabled =
TestFrameworkManager::instance()->groupingEnabled(result->frameworkId);
// lookup existing items // lookup existing items
if (TestTreeItem *toBeModified = parentNode->find(result)) { if (TestTreeItem *toBeModified = parentNode->find(result)) {
// found existing item... Do not remove // found existing item... Do not remove
toBeModified->markForRemoval(false); 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 // modify and when content has changed inform ui
if (toBeModified->modify(result)) { if (toBeModified->modify(result)) {
const QModelIndex &idx = indexForItem(toBeModified); const QModelIndex &idx = indexForItem(toBeModified);
@@ -266,7 +319,8 @@ void TestTreeModel::handleParseResult(const TestParseResult *result, TestTreeIte
// if there's no matching item, add the new one // if there's no matching item, add the new one
TestTreeItem *newItem = result->createTestTreeItem(); TestTreeItem *newItem = result->createTestTreeItem();
QTC_ASSERT(newItem, return); QTC_ASSERT(newItem, return);
parentNode->appendChild(newItem);
insertItemInParent(newItem, parentNode, groupingEnabled);
// new items are checked by default - revalidation of parents might be necessary // new items are checked by default - revalidation of parents might be necessary
if (parentNode->checked() != Qt::Checked) { if (parentNode->checked() != Qt::Checked) {
parentNode->revalidateCheckState(); parentNode->revalidateCheckState();

View File

@@ -56,6 +56,7 @@ public:
QList<TestConfiguration *> getSelectedTests() const; QList<TestConfiguration *> getSelectedTests() const;
void syncTestFrameworks(); void syncTestFrameworks();
void rebuild(const QList<Core::Id> &frameworkIds);
#ifdef WITH_TESTS #ifdef WITH_TESTS
int autoTestsCount() const; int autoTestsCount() const;
@@ -86,6 +87,7 @@ private:
void removeTestRootNodes(); void removeTestRootNodes();
void removeFiles(const QStringList &files); void removeFiles(const QStringList &files);
bool sweepChildren(TestTreeItem *item); bool sweepChildren(TestTreeItem *item);
static void insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled);
explicit TestTreeModel(QObject *parent = 0); explicit TestTreeModel(QObject *parent = 0);
void setupParsingConnections(); void setupParsingConnections();