Commit b0c4a9cc authored by Christian Stenger's avatar Christian Stenger Committed by Christian Stenger
Browse files

Improve support for Qt Quick Tests

parent 9a644d12
......@@ -21,7 +21,8 @@ SOURCES += \
testresult.cpp \
testresultspane.cpp \
testresultmodel.cpp \
testresultdelegate.cpp
testresultdelegate.cpp \
testtreeitemdelegate.cpp
HEADERS += \
testtreeview.h \
......@@ -38,7 +39,8 @@ HEADERS += \
testresult.h \
testresultspane.h \
testresultmodel.h \
testresultdelegate.h
testresultdelegate.h \
testtreeitemdelegate.h
RESOURCES += \
autotest.qrc
......
......@@ -60,8 +60,10 @@ void TestCodeParser::updateTestTree()
m_model->removeAllAutoTests();
m_model->removeAllQuickTests();
const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
if (!session || !session->hasProjects())
if (!session || !session->hasProjects()) {
m_currentProject = 0;
return;
}
m_currentProject = session->startupProject();
if (!m_currentProject)
......@@ -285,13 +287,13 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
}
TestVisitor myVisitor(tc);
myVisitor.accept(declaringDoc->globalNamespace());
const QMap<QString, TestCodeLocation> privSlots = myVisitor.privateSlots();
const QMap<QString, TestCodeLocationAndType> privSlots = myVisitor.privateSlots();
foreach (const QString &privS, privSlots.keys()) {
const TestCodeLocation location = privSlots.value(privS);
TestTreeItem *ttSub = new TestTreeItem(privS, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
const TestCodeLocationAndType locationAndType = privSlots.value(privS);
TestTreeItem *ttSub = new TestTreeItem(privS, locationAndType.m_fileName,
locationAndType.m_type, ttItem);
ttSub->setLine(locationAndType.m_line);
ttSub->setColumn(locationAndType.m_column);
ttItem->appendChild(ttSub);
}
......@@ -374,8 +376,8 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
QmlJS::AST::Node::accept(ast, &qmlVisitor);
const QString tcName = qmlVisitor.testCaseName();
const TestCodeLocation tcLocation = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocation> testFunctions = qmlVisitor.testFunctions();
const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocationAndType> testFunctions = qmlVisitor.testFunctions();
const QModelIndex quickTestRootIndex = m_model->index(1, 0);
TestTreeItem *quickTestRootItem = static_cast<TestTreeItem *>(quickTestRootIndex.internalPointer());
......@@ -417,11 +419,11 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
}
foreach (const QString &func, testFunctions.keys()) {
const TestCodeLocation location = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
const TestCodeLocationAndType locationAndType = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, locationAndType.m_fileName,
locationAndType.m_type, ttItem);
ttSub->setLine(locationAndType.m_line);
ttSub->setColumn(locationAndType.m_column);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
}
......@@ -445,23 +447,23 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
} // end of handling test cases without name property
// construct new/modified TestTreeItem
TestTreeItem *ttItem = new TestTreeItem(tcName, tcLocation.m_fileName,
TestTreeItem::TEST_CLASS, quickTestRootItem);
ttItem->setLine(tcLocation.m_line);
ttItem->setColumn(tcLocation.m_column);
TestTreeItem *ttItem = new TestTreeItem(tcName, tcLocationAndType.m_fileName,
tcLocationAndType.m_type, quickTestRootItem);
ttItem->setLine(tcLocationAndType.m_line);
ttItem->setColumn(tcLocationAndType.m_column);
ttItem->setMainFile(doc->fileName());
foreach (const QString &func, testFunctions.keys()) {
const TestCodeLocation location = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
const TestCodeLocationAndType locationAndType = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, locationAndType.m_fileName,
locationAndType.m_type, ttItem);
ttSub->setLine(locationAndType.m_line);
ttSub->setColumn(locationAndType.m_column);
ttItem->appendChild(ttSub);
}
// update model and internal map
const QString fileName(tcLocation.m_fileName);
const QString fileName(tcLocationAndType.m_fileName);
const QmlJS::Document::Ptr qmlDoc =
QmlJSTools::Internal::ModelManager::instance()->snapshot().document(fileName);
......@@ -497,7 +499,7 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
m_model->addQuickTest(ttItem);
TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision());
ti.setReferencingFile(doc->fileName());
m_quickDocMap.insert(tcLocation.m_fileName, ti);
m_quickDocMap.insert(tcLocationAndType.m_fileName, ti);
}
}
}
......
......@@ -81,6 +81,11 @@ void TestConfiguration::setWorkingDirectory(const QString &workingDirectory)
m_workingDir = workingDirectory;
}
void TestConfiguration::setDisplayName(const QString &displayName)
{
m_displayName = displayName;
}
void TestConfiguration::setEnvironment(const Utils::Environment &env)
{
m_environment = env;
......
......@@ -46,6 +46,7 @@ public:
void setTargetName(const QString &targetName);
void setProFile(const QString &proFile);
void setWorkingDirectory(const QString &workingDirectory);
void setDisplayName(const QString &displayName);
void setEnvironment(const Utils::Environment &env);
void setProject(ProjectExplorer::Project *project);
......@@ -56,6 +57,7 @@ public:
QString targetFile() const { return m_targetFile; }
QString targetName() const { return m_targetName; }
QString workingDirectory() const { return m_workingDir; }
QString displayName() const { return m_displayName; }
Utils::Environment environment() const { return m_environment; }
ProjectExplorer::Project *project() const { return m_project; }
......@@ -72,6 +74,7 @@ private:
QString m_targetFile;
QString m_targetName;
QString m_workingDir;
QString m_displayName;
Utils::Environment m_environment;
ProjectExplorer::Project *m_project;
};
......
......@@ -50,9 +50,9 @@ TestResultsPane::TestResultsPane(QObject *parent) :
QPalette pal;
pal.setColor(QPalette::Window,
Utils::creatorTheme()->color(Utils::Theme::SearchResultWidgetBackgroundColor));
Utils::creatorTheme()->color(Utils::Theme::InfoBarBackground));
pal.setColor(QPalette::WindowText,
Utils::creatorTheme()->color(Utils::Theme::SearchResultWidgetTextColor));
Utils::creatorTheme()->color(Utils::Theme::InfoBarText));
m_summaryWidget = new QFrame;
m_summaryWidget->setPalette(pal);
m_summaryWidget->setAutoFillBackground(true);
......
......@@ -351,8 +351,14 @@ void TestRunner::runTests()
}
ProjectExplorer::Project *project = m_selectedTests.at(0)->project();
if (!project) // add a warning or info to output? possible at all?
if (!project) {
TestResultsPane::instance()->addTestResult(
TestResult(QString(), QString(), QString(), ResultType::MESSAGE_WARN,
tr("*** Project is null - canceling Test Run ***\n"
"Actually only Desktop kits are supported - make sure the "
"current active kit is a Desktop kit.")));
return;
}
ProjectExplorer::ProjectExplorerPlugin *pep = ProjectExplorer::ProjectExplorerPlugin::instance();
ProjectExplorer::Internal::ProjectExplorerSettings pes = pep->projectExplorerSettings();
......
......@@ -24,11 +24,29 @@ namespace Internal {
TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type type, TestTreeItem *parent)
: m_name(name),
m_filePath(filePath),
m_checked(type == ROOT ? Qt::Unchecked : Qt::Checked),
m_type(type),
m_line(0),
m_parent(parent)
{
switch (m_type) {
case ROOT:
m_checked = Qt::Unchecked;
break;
case TEST_CLASS:
case TEST_FUNCTION:
m_checked = Qt::Checked;
break;
case TEST_DATAFUNCTION:
case TEST_SPECIALFUNCTION:
if (m_parent)
m_checked = m_parent->checked() == Qt::PartiallyChecked ? Qt::Unchecked
: m_parent->checked();
else
m_checked = Qt::Unchecked;
break;
default:
m_checked = Qt::Unchecked;
}
}
TestTreeItem::~TestTreeItem()
......@@ -121,19 +139,37 @@ bool TestTreeItem::modifyContent(const TestTreeItem *modified)
void TestTreeItem::setChecked(const Qt::CheckState checkState)
{
switch (m_type) {
case ROOT:
return;
case TEST_FUNCTION:
case TEST_FUNCTION: {
m_checked = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
m_parent->revalidateCheckState();
break;
case TEST_CLASS:
}
case TEST_CLASS: {
Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
foreach (TestTreeItem *child, m_children) {
child->setChecked(usedState);
}
m_checked = usedState;
}
default:
return;
}
}
Qt::CheckState TestTreeItem::checked() const
{
switch (m_type) {
case TEST_CLASS:
case TEST_FUNCTION:
return m_checked;
case TEST_DATAFUNCTION:
case TEST_SPECIALFUNCTION:
return m_parent->m_checked == Qt::PartiallyChecked ? Qt::Unchecked : m_parent->m_checked;
default:
if (m_parent)
return m_parent->m_checked;
}
return Qt::Unchecked;
}
void TestTreeItem::revalidateCheckState()
......@@ -143,8 +179,16 @@ void TestTreeItem::revalidateCheckState()
bool foundChecked = false;
bool foundUnchecked = false;
foreach (const TestTreeItem *child, m_children) {
foundChecked |= (child->m_checked != Qt::Unchecked);
foundUnchecked |= (child->m_checked == Qt::Unchecked);
switch (child->type()) {
case TEST_DATAFUNCTION:
case TEST_SPECIALFUNCTION:
continue;
default:
break;
}
foundChecked |= (child->checked() != Qt::Unchecked);
foundUnchecked |= (child->checked() == Qt::Unchecked);
if (foundChecked && foundUnchecked) {
m_checked = Qt::PartiallyChecked;
return;
......
......@@ -30,7 +30,11 @@ class TestTreeItem
public:
enum Type {
ROOT, TEST_CLASS, TEST_FUNCTION
ROOT,
TEST_CLASS,
TEST_FUNCTION,
TEST_DATAFUNCTION,
TEST_SPECIALFUNCTION
};
TestTreeItem(const QString &name, const QString &filePath, Type type, TestTreeItem *parent = 0);
......@@ -57,7 +61,7 @@ public:
QString mainFile() const { return m_mainFile; }
void setMainFile(const QString &mainFile) { m_mainFile = mainFile; }
void setChecked(const Qt::CheckState checked);
Qt::CheckState checked() const { return m_checked; }
Qt::CheckState checked() const;
Type type() const { return m_type; }
void setParent(TestTreeItem *parent) { m_parent = parent; }
......
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** 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 Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
**
****************************************************************************/
#include "testtreeitemdelegate.h"
#include "testtreemodel.h"
#include <QPainter>
namespace Autotest {
namespace Internal {
TestTreeItemDelegate::TestTreeItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
TestTreeItemDelegate::~TestTreeItemDelegate()
{
}
void TestTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
bool italic = index.data(ItalicRole).toBool();
if (italic) {
QFont font(option.font);
font.setItalic(true);
opt.font = font;
// correct margin of items without a checkbox (except for root items)
QStyleOptionButton styleOpt;
styleOpt.initFrom(opt.widget);
const QSize sz; // no text, no icon - we just need the size of the check box
QSize cbSize = opt.widget->style()->sizeFromContents(QStyle::CT_CheckBox, &styleOpt, sz);
// the 6 results from hard coded margins of the checkbox itself (2x2) and the item (1x2)
opt.rect.setLeft(opt.rect.left() + cbSize.width() + 6);
// HACK make sure the pixels that have been moved right are painted for selections
if (opt.state & QStyle::State_Selected) {
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled
? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
cg = QPalette::Inactive;
painter->fillRect(option.rect, opt.palette.brush(cg, QPalette::Highlight));
}
}
QStyledItemDelegate::paint(painter, opt, index);
}
} // namespace Internal
} // namespace Autotest
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** 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 Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
**
****************************************************************************/
#ifndef TESTTREEITEMDELEGATE_H
#define TESTTREEITEMDELEGATE_H
#include <QStyledItemDelegate>
namespace Autotest {
namespace Internal {
class TestTreeItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
TestTreeItemDelegate(QObject *parent = 0);
~TestTreeItemDelegate();
public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
} // namespace Internal
} // namespace Autotest
#endif // TESTTREEITEMDELEGATE_H
......@@ -135,6 +135,8 @@ static QIcon testTreeIcon(TestTreeItem::Type type)
QIcon(QLatin1String(":/images/class.png")),
QIcon(QLatin1String(":/images/func.png"))
};
if (type >= 3)
return icons[2];
return icons[type];
}
......@@ -169,15 +171,46 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole:
return testTreeIcon(item->type());
case Qt::CheckStateRole:
if (item->type() == TestTreeItem::ROOT)
switch (item->type()) {
case TestTreeItem::ROOT:
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
return QVariant();
return item->checked();
case LinkRole:
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;
}
}
// TODO ?
return QVariant();
......@@ -222,15 +255,18 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
switch(item->type()) {
case TestTreeItem::TEST_CLASS:
if (item->name().isEmpty())
return Qt::ItemIsSelectable | Qt::ItemIsTristate;
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::ItemIsSelectable;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
case TestTreeItem::ROOT:
default:
return Qt::ItemIsEnabled;
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
default:
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
......@@ -261,18 +297,38 @@ bool TestTreeModel::hasTests() const
static void addProjectInformation(TestConfiguration *config, const QString &filePath)
{
const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
if (!session || !session->hasProjects())
return;
ProjectExplorer::Project *project = session->startupProject();
if (!project)
return;
QString targetFile;
QString targetName;
QString workDir;
QString proFile;
QString displayName;
Utils::Environment env;
ProjectExplorer::Project *project = 0;
bool hasDesktopTarget = false;
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
QList<CppTools::ProjectPart::Ptr> projParts = cppMM->projectPart(filePath);
QList<CppTools::ProjectPart::Ptr> projParts = cppMM->projectInfo(project).projectParts();
if (!projParts.empty()) {
proFile = projParts.at(0)->projectFile;
project = projParts.at(0)->project; // necessary to grab this here? or should this be the current active startup project anyway?
foreach (const CppTools::ProjectPart::Ptr &part, projParts) {
foreach (const CppTools::ProjectFile currentFile, part->files) {
if (currentFile.path == filePath) {
proFile = part->projectFile;
displayName = part->displayName;
break;
}
}
if (!proFile.isEmpty()) // maybe better use a goto instead of the break above??
break;
}
}
if (project) {
if (auto target = project->activeTarget()) {
ProjectExplorer::BuildTargetInfoList appTargets = target->applicationTargets();
......@@ -289,6 +345,7 @@ static void addProjectInformation(TestConfiguration *config, const QString &file
if (ProjectExplorer::LocalApplicationRunConfiguration *localRunConfiguration
= qobject_cast<ProjectExplorer::LocalApplicationRunConfiguration *>(rc)) {
if (localRunConfiguration->executable() == targetFile) {
hasDesktopTarget = true;
workDir = Utils::FileUtils::normalizePathName(
localRunConfiguration->workingDirectory());
ProjectExplorer::EnvironmentAspect *envAsp
......@@ -300,12 +357,19 @@ static void addProjectInformation(TestConfiguration *config, const QString &file
}
}
}
config->setTargetFile(targetFile);
config->setTargetName(targetName);
config->setWorkingDirectory(workDir);
config->setProFile(proFile);
config->setEnvironment(env);
config->setProject(project);
if (hasDesktopTarget) {
config->setTargetFile(targetFile);
config->setTargetName(targetName);
config->setWorkingDirectory(workDir);
config->setProFile(proFile);
config->setEnvironment(env);
config->setProject(project);
config->setDisplayName(displayName);
} else {
config->setProFile(proFile);
config->setDisplayName(displayName);
}
}
QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
......@@ -392,17 +456,18 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
QMap<QString, TestConfiguration *> foundMains;
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)) {
foundMains[mainFile]->setTestCaseCount(tc->testCaseCount() + 1);
} else {
TestConfiguration *tc = new TestConfiguration(QString(), QStringList());
tc->setTestCaseCount(1);
addProjectInformation(tc, mainFile);
foundMains.insert(mainFile, tc);
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)) {
foundMains[mainFile]->setTestCaseCount(tc->testCaseCount() + 1);
} else {
TestConfiguration *tc = new TestConfiguration(QString(), QStringList());
tc->setTestCaseCount(1);
addProjectInformation(tc, mainFile);
foundMains.insert(mainFile, tc);