qtestlibplugin.cpp 15.64 KiB
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
#include "qtestlibplugin.h"
#include <QtCore/qplugin.h>
#include <QIcon>
#include <QDebug>
#include <QKeySequence>
#include <QAction>
#include <QHeaderView>
#include <QDomDocument>
#include <QTemporaryFile>
#include <texteditor/TextEditorInterfaces>
#include <Qt4IProjectManagers>
#include <QFileInfo>
#include <QDir>
#include <QStandardItemModel>
#include <QTreeView>
#include <QTextEdit>
#include <QSplitter>
#include <QVBoxLayout>
#include <QComboBox>
#include <QLabel>
using namespace QTestLib::Internal;
static QString incidentString(QTestFunction::IncidentType type)
{
static QMap<QTestFunction::IncidentType, QString> strings;
if (strings.empty()) {
strings.insert(QTestFunction::Pass, QObject::tr("Pass"));
strings.insert(QTestFunction::XFail, QObject::tr("Expected Failure"));
strings.insert(QTestFunction::Fail, QObject::tr("Failure"));
strings.insert(QTestFunction::XPass, QObject::tr("Expected Pass"));
}
return strings.value(type, QString());
}
static QString messageString(QTestFunction::MessageType type)
{
static QMap<QTestFunction::MessageType, QString> strings;
if (strings.empty()) {
strings.insert(QTestFunction::Warning, QObject::tr("Warning"));
strings.insert(QTestFunction::QWarning, QObject::tr("Qt Warning"));
strings.insert(QTestFunction::QDebug, QObject::tr("Qt Debug"));
strings.insert(QTestFunction::QSystem, QObject::tr("Critical"));
strings.insert(QTestFunction::QFatal, QObject::tr("Fatal"));
strings.insert(QTestFunction::Skip, QObject::tr("Skipped"));
strings.insert(QTestFunction::Info, QObject::tr("Info"));
}
return strings.value(type, QString());
}
static QTestFunction::IncidentType stringToIncident(const QString &str)
{
if (str == QLatin1String("pass"))
return QTestFunction::Pass;
else if (str == QLatin1String("fail"))
return QTestFunction::Fail;
else if (str == QLatin1String("xfail"))
return QTestFunction::XFail;
else if (str == QLatin1String("xpass"))
return QTestFunction::XPass;
return QTestFunction::Fail; // ...
}
static QTestFunction::MessageType stringToMessageType(const QString &str)
{
if (str == QLatin1String("warn"))
return QTestFunction::Warning;
else if (str == QLatin1String("system"))
return QTestFunction::QSystem;
else if (str == QLatin1String("qdebug"))
return QTestFunction::QDebug;
else if (str == QLatin1String("qwarn"))
return QTestFunction::QWarning;
else if (str == QLatin1String("qfatal"))
return QTestFunction::QFatal;
else if (str == QLatin1String("skip"))
return QTestFunction::Skip;
else if (str == QLatin1String("info"))
return QTestFunction::Info;
return QTestFunction::QSystem; // ...
}
// -----------------------------------
QTestLibPlugin::QTestLibPlugin() :
m_projectExplorer(0),
m_core(0),
m_outputPane(0)
{
}
QTestLibPlugin::~QTestLibPlugin()
{
if (m_core && m_outputPane)
m_core->pluginManager()->removeObject(m_outputPane);
}
bool QTestLibPlugin::init(ExtensionSystem::PluginManagerInterface *app, QString * /*error_message*/)
{
m_core = app->getObject<Core::ICore>();
m_projectExplorer = app->getObject<ProjectExplorer::ProjectExplorerPlugin>();
connect(m_projectExplorer->qObject(), SIGNAL(aboutToExecuteProject(ProjectExplorer::Project *)),
this, SLOT(projectRunHook(ProjectExplorer::Project *)));
m_outputPane = new QTestOutputPane(this);
app->addObject(m_outputPane);
return true;
}
void QTestLibPlugin::extensionsInitialized()
{
}
void QTestLibPlugin::projectRunHook(ProjectExplorer::Project *proj)
{
return; //NBS TODO QTestlibplugin
if (!proj)
return;
m_projectDirectory = QString();
//NBS proj->setExtraApplicationRunArguments(QStringList());
//NBS proj->setCustomApplicationOutputHandler(0);
const QVariant config; //NBS = proj->projectProperty(ProjectExplorer::Constants::P_CONFIGVAR);
if (!config.toStringList().contains(QLatin1String("qtestlib")))
return;
{
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);
tempFile.open();
m_outputFile = tempFile.fileName();
}
//NBS proj->setCustomApplicationOutputHandler(this);
//NBS proj->setExtraApplicationRunArguments(QStringList() << QLatin1String("-xml") << QLatin1String("-o") << m_outputFile);
const QString proFile = proj->fileName();
const QFileInfo fi(proFile);
if (QFile::exists(fi.absolutePath()))
m_projectDirectory = fi.absolutePath();
}
void QTestLibPlugin::clear()
{
m_projectExplorer->applicationOutputWindow()->clear();
}
void QTestLibPlugin::appendOutput(const QString &out)
{
m_projectExplorer->applicationOutputWindow()->appendOutput(out);
}
void QTestLibPlugin::processExited(int exitCode)
{
m_projectExplorer->applicationOutputWindow()->processExited(exitCode);
QFile f(m_outputFile);
if (!f.open(QIODevice::ReadOnly))
return;
QDomDocument doc;
if (!doc.setContent(&f))
return;
f.close();
f.remove();
m_outputPane->clearContents();
const QString testFunctionTag = QLatin1String("TestFunction");
const QString nameAttr = QLatin1String("name");
const QString typeAttr = QLatin1String("type");
const QString incidentTag = QLatin1String("Incident");
const QString fileAttr = QLatin1String("file");
const QString lineAttr = QLatin1String("line");
const QString messageTag = QLatin1String("Message");
const QString descriptionItem = QLatin1String("Description");
for (QDomElement testElement = doc.documentElement().firstChildElement();
!testElement.isNull(); testElement = testElement.nextSiblingElement()) {
if (testElement.tagName() != testFunctionTag)
continue;
QTestFunction *function = new QTestFunction(testElement.attribute(nameAttr));
for (QDomElement e = testElement.firstChildElement();
!e.isNull(); e = e.nextSiblingElement()) {
const QString type = e.attribute(typeAttr);
if (e.tagName() == incidentTag) {
QString file = e.attribute(fileAttr);
if (!file.isEmpty()
&& QFileInfo(file).isRelative()
&& !m_projectDirectory.isEmpty()) {
QFileInfo fi(m_projectDirectory, file);
if (fi.exists())
file = fi.absoluteFilePath();
}
const QString line = e.attribute(lineAttr);
const QString details = e.text();
QTestFunction::IncidentType itype = stringToIncident(type);
function->addIncident(itype, file, line, details);
} else if (e.tagName() == messageTag ) {
QTestFunction::MessageType msgType = stringToMessageType(type);
function->addMessage(msgType, e.namedItem(descriptionItem).toElement().text());
}
}
m_outputPane->addFunction(function);
}
m_outputPane->show();
}
// -------- QTestFunction
void QTestFunction::addIncident(IncidentType type,
const QString &file,
const QString &line,
const QString &details)
{
QStandardItem *status = new QStandardItem(incidentString(type));
status->setData(QVariant::fromValue(type));
switch (type) {
case QTestFunction::Pass: status->setForeground(Qt::green); break;
case QTestFunction::Fail: status->setForeground(Qt::red); break;
case QTestFunction::XFail: status->setForeground(Qt::darkMagenta); break;
case QTestFunction::XPass: status->setForeground(Qt::darkGreen); break;
}
QStandardItem *location = new QStandardItem;
if (!file.isEmpty()) {
location->setText(file + QLatin1Char(':') + line);
location->setForeground(Qt::red);
QTestLocation loc;
loc.file = file;
loc.line = line;
location->setData(QVariant::fromValue(loc));
}
appendRow(QList<QStandardItem *>() << status << location);
if (!details.isEmpty()) {
status->setColumnCount(2);
status->appendRow(QList<QStandardItem *>() << new QStandardItem() << new QStandardItem(details));
}
}
void QTestFunction::addMessage(MessageType type, const QString &text)
{
QStandardItem *status = new QStandardItem(messageString(type));
status->setData(QVariant::fromValue(type));
QStandardItem *msg = new QStandardItem(text);
appendRow(QList<QStandardItem *>() << status << msg);
}
bool QTestFunction::indexHasIncidents(const QModelIndex &function, IncidentType type)
{
if (!function.isValid())
return false;
const QAbstractItemModel *m = function.model();
if (!m->hasChildren(function))
return false;
const int rows = m->rowCount(function);
for (int row = 0; row < rows; ++row) {
const QModelIndex child = m->index(row, 0, function);
QVariant tag = child.data(Qt::UserRole + 1);
if (tag.type() != QVariant::UserType
|| tag.userType() != qMetaTypeId<QTestFunction::IncidentType>())
continue;
if (tag.value<QTestFunction::IncidentType>() == type)
return true;
}
return false;
}
// -------------- QTestOutputPane
QTestOutputPane::QTestOutputPane(QTestLibPlugin *plugin) :
QObject(plugin),
m_plugin(plugin),
m_widget(0),
m_model(new QStandardItemModel(this))
{
clearContents();
}
void QTestOutputPane::addFunction(QTestFunction *function)
{
m_model->appendRow(function);
}
QWidget *QTestOutputPane::outputWidget(QWidget *parent)
{
if (!m_widget)
m_widget = new QTestOutputWidget(m_model, m_plugin->coreInterface(), parent);
return m_widget;
}
QString QTestOutputPane::name() const
{
return tr("Test Results");
}
void QTestOutputPane::clearContents()
{
m_model->clear();
m_model->setColumnCount(2);
m_model->setHorizontalHeaderLabels(QStringList() << tr("Result") << tr("Message"));
}
void QTestOutputPane::visibilityChanged(bool visible)
{
Q_UNUSED(visible)
}
void QTestOutputPane::show()
{
if (m_widget)
m_widget->expand();
emit showPage();
}
// -------- QTestOutputFilter
bool QTestOutputFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (sourceParent.isValid()) {
return true;
}
QModelIndex idx = sourceModel()->index(sourceRow, 0);
if (QTestFunction::indexHasIncidents(idx, m_filter))
return true;
else
return false;
}
// ------- QTestOutputWidget
QTestOutputWidget::QTestOutputWidget(QStandardItemModel *model, Core::ICore *coreInterface, QWidget *parent):
QWidget(parent),
m_coreInterface(coreInterface),
m_model(model),
m_resultsView(new QTreeView(this)),
m_filterCombo(new QComboBox(this)),
m_filterModel(new QTestOutputFilter(this))
{
m_resultsView->setModel(model);
m_resultsView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_resultsView->header()->setStretchLastSection(true);
connect(m_resultsView, SIGNAL(activated(const QModelIndex &)),
this, SLOT(gotoLocation(QModelIndex)));
m_filterCombo->addItem(tr("All Incidents"));
m_filterCombo->addItem(incidentString(QTestFunction::Fail), QVariant::fromValue(QTestFunction::Fail));
m_filterCombo->addItem(incidentString(QTestFunction::Pass), QVariant::fromValue(QTestFunction::Pass));
m_filterCombo->addItem(incidentString(QTestFunction::XFail), QVariant::fromValue(QTestFunction::XFail));
m_filterCombo->addItem(incidentString(QTestFunction::XPass), QVariant::fromValue(QTestFunction::XPass));
connect(m_filterCombo, SIGNAL(activated(int)),
this, SLOT(activateComboFilter(int)));
QHBoxLayout *filterLayout = new QHBoxLayout;
filterLayout->addWidget(new QLabel(tr("Show Only:"), this));
filterLayout->addWidget(m_filterCombo);
filterLayout->addStretch();
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addLayout(filterLayout);
layout->addWidget(m_resultsView);
m_filterModel->setDynamicSortFilter(true);
m_filterModel->setSourceModel(m_model);
}
void QTestOutputWidget::expand()
{
/*
const QAbstractItemModel *m = m_resultsView->model();
for (int r = 0, count = m->rowCount(); r < count; ++r) {
m_resultsView->expand(m->index(r, 0));
}
*/
m_resultsView->expandAll();
m_resultsView->header()->resizeSections(QHeaderView::ResizeToContents);
}
void QTestOutputWidget::activateComboFilter(int index)
{
QVariant tag = m_filterCombo->itemData(index);
if (!tag.isValid()) {
if (m_resultsView->model() != m_model)
m_resultsView->setModel(m_model);
} else {
QTestFunction::IncidentType incident = tag.value<QTestFunction::IncidentType>();
m_filterModel->setIncidentFilter(incident);
if (m_resultsView->model() != m_filterModel)
m_resultsView->setModel(m_filterModel);
}
expand();
}
void QTestOutputWidget::gotoLocation(QModelIndex index)
{
if (!index.isValid())
return;
if (m_resultsView->model() == m_filterModel)
index = m_filterModel->mapToSource(index);
if (!index.isValid())
return;
const QAbstractItemModel *m = index.model();
QModelIndex parent = index.parent();
if (!parent.isValid())
return;
QModelIndex functionIndex = parent;
QModelIndex failureIndex = index;
QModelIndex grandParent = parent.parent();
if (grandParent.isValid()) {
functionIndex = grandParent;
failureIndex = parent;
}
if (!functionIndex.isValid())
return;
QModelIndex locationIndex = m->index(failureIndex.row(), 1, functionIndex);
if (!locationIndex.isValid())
return;
QVariant tag = locationIndex.data(Qt::UserRole + 1);
if (tag.type() != QVariant::UserType
|| tag.userType() != qMetaTypeId<QTestLocation>())
return;
QTestLocation loc = tag.value<QTestLocation>();
m_coreInterface->editorManager()->openEditor(loc.file);
Core::EditorInterface *edtIface = m_coreInterface->editorManager()->currentEditor();
if (!edtIface)
return;
TextEditor::ITextEditor *editor =
qobject_cast<TextEditor::ITextEditor*>(edtIface->qObject());
if (!editor)
return;
editor->gotoLine(loc.line.toInt());
}
Q_EXPORT_PLUGIN(QTestLibPlugin)