Files
qt-creator/src/plugins/autotest/quick/quicktesttreeitem.cpp
Christian Stenger efa7a5a4d4 AutoTest: Add suite layer for tests
Test suites are used in several test frameworks. To avoid
fiddling around with the types and their meaning on the
frameworks side just add another type (TestSuite) that can
be used.
Frameworks can use or ignore this type as it is done already
for other types.

Change-Id: I6c2a312a32ce5716f55b49303e2b1a0f6f8e8da4
Reviewed-by: David Schulz <david.schulz@qt.io>
2019-05-16 07:41:38 +00:00

457 lines
16 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** 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.
**
** 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.
**
****************************************************************************/
#include "quicktesttreeitem.h"
#include "quicktestconfiguration.h"
#include "quicktestframework.h"
#include "quicktestparser.h"
#include "../testframeworkmanager.h"
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
namespace Autotest {
namespace Internal {
TestTreeItem *QuickTestTreeItem::copyWithoutChildren()
{
QuickTestTreeItem *copied = new QuickTestTreeItem;
copied->copyBasicDataFrom(this);
return copied;
}
QVariant QuickTestTreeItem::data(int column, int role) const
{
switch (role) {
case Qt::DisplayRole:
if (type() == TestCase && name().isEmpty())
return QCoreApplication::translate("QuickTestTreeItem", "<unnamed>");
break;
case Qt::ToolTipRole:
if (type() == TestCase && name().isEmpty())
return QCoreApplication::translate("QuickTestTreeItem",
"<p>Give all test cases a name to ensure correct "
"behavior when running test cases and to be able to "
"select them.</p>");
break;
case Qt::CheckStateRole:
switch (type()) {
case TestDataFunction:
case TestSpecialFunction:
case TestDataTag:
return QVariant();
case TestCase:
return name().isEmpty() ? QVariant() : checked();
case TestFunctionOrSet:
return (parentItem() && !parentItem()->name().isEmpty()) ? checked() : QVariant();
default:
return checked();
}
case ItalicRole:
switch (type()) {
case TestDataFunction:
case TestSpecialFunction:
return true;
case TestCase:
return name().isEmpty();
case TestFunctionOrSet:
return parentItem() ? parentItem()->name().isEmpty() : false;
default:
return false;
}
default:
break;
}
return TestTreeItem::data(column, role);
}
Qt::ItemFlags QuickTestTreeItem::flags(int column) const
{
// handle unnamed quick tests and their functions
switch (type()) {
case TestCase:
if (name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
break;
case TestFunctionOrSet:
if (parentItem()->name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
break;
default: {} // avoid warning regarding unhandled enum values
}
return TestTreeItem::flags(column);
}
bool QuickTestTreeItem::canProvideTestConfiguration() const
{
switch (type()) {
case TestCase:
return !name().isEmpty();
case TestFunctionOrSet:
return !parentItem()->name().isEmpty();
default:
return false;
}
}
bool QuickTestTreeItem::canProvideDebugConfiguration() const
{
return canProvideTestConfiguration();
}
TestConfiguration *QuickTestTreeItem::testConfiguration() const
{
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
QTC_ASSERT(project, return nullptr);
QuickTestConfiguration *config = nullptr;
switch (type()) {
case TestCase: {
const QString testName = name();
QStringList testFunctions;
forFirstLevelChildren([&testFunctions, &testName](TestTreeItem *child) {
if (child->type() == TestTreeItem::TestFunctionOrSet)
testFunctions << testName + "::" + child->name();
});
config = new QuickTestConfiguration;
config->setTestCases(testFunctions);
config->setProjectFile(proFile());
config->setProject(project);
break;
}
case TestFunctionOrSet: {
TestTreeItem *parent = parentItem();
QStringList testFunction(parent->name() + "::" + name());
config = new QuickTestConfiguration;
config->setTestCases(testFunction);
config->setProjectFile(parent->proFile());
config->setProject(project);
break;
}
default:
return nullptr;
}
if (config)
config->setInternalTargets(internalTargets());
return config;
}
static void testConfigurationFromCheckState(const TestTreeItem *item,
QHash<QString, QuickTestConfiguration *> &foundProFiles)
{
QTC_ASSERT(item, return);
if (item->type() == TestTreeItem::GroupNode) {
for (int row = 0, count = item->childCount(); row < count; ++row)
testConfigurationFromCheckState(item->childAt(row), foundProFiles);
return;
}
QTC_ASSERT(item->type() == TestTreeItem::TestCase, return);
QuickTestConfiguration *tc = nullptr;
if (item->checked() == Qt::Unchecked)
return;
const QString testName = item->name();
QStringList testFunctions;
item->forFirstLevelChildren([&testFunctions, &testName](TestTreeItem *child) {
if (child->checked() == Qt::Checked && child->type() == TestTreeItem::TestFunctionOrSet)
testFunctions << testName + "::" + child->name();
});
if (foundProFiles.contains(item->proFile())) {
tc = foundProFiles[item->proFile()];
QStringList oldFunctions(tc->testCases());
oldFunctions << testFunctions;
tc->setTestCases(oldFunctions);
} else {
tc = new QuickTestConfiguration;
tc->setTestCases(testFunctions);
tc->setProjectFile(item->proFile());
tc->setProject(ProjectExplorer::SessionManager::startupProject());
tc->setInternalTargets(item->internalTargets());
foundProFiles.insert(item->proFile(), tc);
}
}
TestConfiguration *QuickTestTreeItem::debugConfiguration() const
{
QuickTestConfiguration *config = static_cast<QuickTestConfiguration *>(testConfiguration());
if (config)
config->setRunMode(TestRunMode::Debug);
return config;
}
struct Tests {
int testCount = 0;
QSet<QString> internalTargets;
};
static void addTestsForItem(Tests &tests, const TestTreeItem *item)
{
tests.testCount += item->childCount();
tests.internalTargets = item->internalTargets();
}
QList<TestConfiguration *> QuickTestTreeItem::getAllTestConfigurations() const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project || type() != Root)
return result;
QHash<QString, Tests> testsForProfile;
forFirstLevelChildren([&testsForProfile](TestTreeItem *child) {
// unnamed Quick Tests must be handled separately
if (child->name().isEmpty()) {
child->forFirstLevelChildren([&testsForProfile](TestTreeItem *grandChild) {
const QString &proFile = grandChild->proFile();
++(testsForProfile[proFile].testCount);
testsForProfile[proFile].internalTargets = grandChild->internalTargets();
});
return;
}
// named Quick Test
if (child->type() == TestCase) {
addTestsForItem(testsForProfile[child->proFile()], child);
} else if (child->type() == GroupNode) {
child->forFirstLevelChildren([&testsForProfile](TestTreeItem *grandChild) {
addTestsForItem(testsForProfile[grandChild->proFile()], grandChild);
});
}
});
// create TestConfiguration for each project file
for (auto it = testsForProfile.begin(), end = testsForProfile.end(); it != end; ++it) {
QuickTestConfiguration *tc = new QuickTestConfiguration;
tc->setTestCaseCount(it.value().testCount);
tc->setProjectFile(it.key());
tc->setProject(project);
tc->setInternalTargets(it.value().internalTargets);
result << tc;
}
return result;
}
QList<TestConfiguration *> QuickTestTreeItem::getSelectedTestConfigurations() const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project || type() != Root)
return result;
QHash<QString, QuickTestConfiguration *> foundProFiles;
forFirstLevelChildren([&foundProFiles](TestTreeItem *child) {
// unnamed Quick Tests cannot get selected explicitly - only handle named Quick Tests
if (!child->name().isEmpty())
testConfigurationFromCheckState(child, foundProFiles);
});
for (auto it = foundProFiles.begin(), end = foundProFiles.end(); it != end; ++it) {
QuickTestConfiguration *config = it.value();
if (!config->unnamedOnly())
result << config;
else
delete config;
}
return result;
}
QList<TestConfiguration *> QuickTestTreeItem::getTestConfigurationsForFile(const Utils::FileName &fileName) const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project || type() != Root)
return result;
QHash<TestTreeItem *, QStringList> testFunctions;
const QString &file = fileName.toString();
forAllChildren([&testFunctions, &file](TestTreeItem *node) {
if (node->type() == Type::TestFunctionOrSet && node->filePath() == file) {
QTC_ASSERT(node->parentItem(), return);
TestTreeItem *testCase = node->parentItem();
QTC_ASSERT(testCase->type() == Type::TestCase, return);
if (testCase->name().isEmpty())
return;
testFunctions[testCase] << testCase->name() + "::" + node->name();
}
});
for (auto it = testFunctions.cbegin(), end = testFunctions.cend(); it != end; ++it) {
TestConfiguration *tc = it.key()->testConfiguration();
QTC_ASSERT(tc, continue);
tc->setTestCases(it.value());
result << tc;
}
return result;
}
TestTreeItem *QuickTestTreeItem::find(const TestParseResult *result)
{
QTC_ASSERT(result, return nullptr);
switch (type()) {
case Root:
if (result->name.isEmpty())
return unnamedQuickTests();
if (TestFrameworkManager::instance()->groupingEnabled(result->frameworkId)) {
const QString path = QFileInfo(result->fileName).absolutePath();
TestTreeItem *group = findFirstLevelChild([path](TestTreeItem *group) {
return group->filePath() == path;
});
return group ? group->findChildByFile(result->fileName) : nullptr;
}
return findChildByFile(result->fileName);
case GroupNode:
return findChildByFile(result->fileName);
case TestCase:
return name().isEmpty() ? findChildByNameAndFile(result->name, result->fileName)
: findChildByName(result->name);
default:
return nullptr;
}
}
TestTreeItem *QuickTestTreeItem::findChild(const TestTreeItem *other)
{
QTC_ASSERT(other, return nullptr);
const Type otherType = other->type();
switch (type()) {
case Root:
if (otherType == TestCase && other->name().isEmpty())
return unnamedQuickTests();
return findChildByFileAndType(other->filePath(), otherType);
case GroupNode:
return findChildByFileAndType(other->filePath(), otherType);
case TestCase:
if (otherType != TestFunctionOrSet && otherType != TestDataFunction && otherType != TestSpecialFunction)
return nullptr;
return name().isEmpty() ? findChildByNameAndFile(other->name(), other->filePath())
: findChildByName(other->name());
default:
return nullptr;
}
}
bool QuickTestTreeItem::modify(const TestParseResult *result)
{
QTC_ASSERT(result, return false);
switch (type()) {
case TestCase:
return result->name.isEmpty() ? false : modifyTestCaseOrSuiteContent(result);
case TestFunctionOrSet:
case TestDataFunction:
case TestSpecialFunction:
return name().isEmpty() ? modifyLineAndColumn(result)
: modifyTestFunctionContent(result);
default:
return false;
}
}
bool QuickTestTreeItem::lessThan(const TestTreeItem *other, TestTreeItem::SortMode mode) const
{
// handle special item (<unnamed>)
if (name().isEmpty())
return false;
if (other->name().isEmpty())
return true;
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);
}
bool QuickTestTreeItem::removeOnSweepIfEmpty() const
{
return TestTreeItem::removeOnSweepIfEmpty()
|| (type() == TestCase && name().isEmpty()); // remove pseudo item '<unnamed>'
}
TestTreeItem *QuickTestTreeItem::createParentGroupNode() const
{
const QFileInfo fileInfo(filePath());
const QFileInfo base(fileInfo.absolutePath());
return new QuickTestTreeItem(base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode);
}
bool QuickTestTreeItem::isGroupable() const
{
return type() == TestCase && !name().isEmpty() && !filePath().isEmpty();
}
QSet<QString> QuickTestTreeItem::internalTargets() const
{
QSet<QString> result;
const auto cppMM = CppTools::CppModelManager::instance();
const auto projectInfo = cppMM->projectInfo(ProjectExplorer::SessionManager::startupProject());
for (const CppTools::ProjectPart::Ptr &projectPart : projectInfo.projectParts()) {
if (projectPart->buildTargetType != CppTools::ProjectPart::Executable)
continue;
if (projectPart->projectFile == proFile()) {
result.insert(projectPart->buildSystemTarget);
break;
}
}
return result;
}
void QuickTestTreeItem::markForRemovalRecursively(const QString &filePath)
{
static const Core::Id id = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix(
QuickTest::Constants::FRAMEWORK_NAME);
TestTreeItem::markForRemovalRecursively(filePath);
auto parser = dynamic_cast<QuickTestParser *>(TestFrameworkManager::instance()
->testParserForTestFramework(id));
const QString proFile = parser->projectFileForMainCppFile(filePath);
if (!proFile.isEmpty()) {
TestTreeItem *root = TestFrameworkManager::instance()->rootNodeForTestFramework(id);
root->forAllChildren([proFile](TestTreeItem *it) {
if (it->proFile() == proFile)
it->markForRemoval(true);
});
}
}
TestTreeItem *QuickTestTreeItem::unnamedQuickTests() const
{
if (type() != Root)
return nullptr;
return findFirstLevelChild([](TestTreeItem *child) { return child->name().isEmpty(); });
}
} // namespace Internal
} // namespace Autotest