Commit 2f4139e5 authored by Christian Stenger's avatar Christian Stenger Committed by Christian Stenger
Browse files

Initial commit

parents
TEMPLATE = subdirs
CONFIG += ordered
SUBDIRS += plugins/autotest shared
QMAKE_EXTRA_TARGETS = docs install_docs # dummy targets for consistency
{
\"Name\" : \"AutoTest\",
\"Version\" : \"$$QTCREATOR_VERSION\",
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
\"Experimental\" : true,
\"Vendor\" : \"Digia Plc\",
\"Copyright\" : \"(C) 2014 Digia Plc\",
\"License\" : [ \"Commercial Usage\",
\"\",
\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Digia.\"
],
\"Description\" : \"Auto Test plugin.\",
\"Url\" : \"http://qt.digia.com\",
$$dependencyList
}
TARGET = AutoTest
TEMPLATE = lib
PROVIDER = Digia
include(../../qtcreatorplugin.pri)
include(autotest_dependencies.pri)
DEFINES += AUTOTEST_LIBRARY
SOURCES += \
testtreeview.cpp \
testtreemodel.cpp \
testtreeitem.cpp \
testvisitor.cpp \
testinfo.cpp \
testcodeparser.cpp \
autotestplugin.cpp
HEADERS += \
testtreeview.h \
testtreemodel.h \
testtreeitem.h \
testvisitor.h \
testinfo.h \
testcodeparser.h \
autotestplugin.h \
autotest_global.h \
autotestconstants.h
RESOURCES += \
autotest.qrc
<RCC>
<qresource prefix="/">
<file>images/class.png</file>
<file>images/func.png</file>
<file>images/expand.png</file>
<file>images/collapse.png</file>
<file>images/sort.png</file>
</qresource>
</RCC>
QTC_PLUGIN_NAME = AutoTest
QTC_PLUGIN_DEPENDS += \
coreplugin \
projectexplorer \
cpptools
QTC_LIB_DEPENDS += \
cplusplus \
utils
#QTC_PLUGIN_RECOMMENDS += \
/****************************************************************************
**
** 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 AUTOTEST_GLOBAL_H
#define AUTOTEST_GLOBAL_H
#include <QtGlobal>
#if defined(AUTOTEST_LIBRARY)
# define AUTOTESTSHARED_EXPORT Q_DECL_EXPORT
#else
# define AUTOTESTSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // AUTOTEST_GLOBAL_H
/****************************************************************************
**
** 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 AUTOTESTCONSTANTS_H
#define AUTOTESTCONSTANTS_H
namespace Autotest {
namespace Constants {
const char ACTION_ID[] = "AutoTest.Action";
const char MENU_ID[] = "AutoTest.Menu";
const char AUTOTEST_ID[] = "AutoTest.ATP";
const char AUTOTEST_CONTEXT[] = "Auto Tests";
} // namespace Autotest
} // namespace Constants
#endif // AUTOTESTCONSTANTS_H
/****************************************************************************
**
** 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 "autotestplugin.h"
#include "autotestconstants.h"
#include "testtreeview.h"
#include "testtreemodel.h"
#include <coreplugin/icore.h>
#include <coreplugin/icontext.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/coreconstants.h>
#include <QAction>
#include <QMessageBox>
#include <QMainWindow>
#include <QMenu>
#include <QtPlugin>
using namespace Autotest::Internal;
AutotestPlugin::AutotestPlugin()
{
// Create your members
}
AutotestPlugin::~AutotestPlugin()
{
// Unregister objects from the plugin manager's object pool
// Delete members
TestTreeModel *model = TestTreeModel::instance();
delete model;
}
bool AutotestPlugin::initialize(const QStringList &arguments, QString *errorString)
{
// Register objects in the plugin manager's object pool
// Load settings
// Add actions to menus
// Connect to other plugins' signals
// In the initialize function, a plugin can be sure that the plugins it
// depends on have initialized their members.
Q_UNUSED(arguments)
Q_UNUSED(errorString)
QAction *action = new QAction(tr("Autotest action"), this);
Core::Command *cmd = Core::ActionManager::registerAction(action, Constants::ACTION_ID,
Core::Context(Core::Constants::C_GLOBAL));
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+Meta+A")));
connect(action, SIGNAL(triggered()), this, SLOT(triggerAction()));
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::MENU_ID);
menu->menu()->setTitle(tr("Tests"));
menu->addAction(cmd);
Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu);
addAutoReleasedObject(new TestViewFactory);
return true;
}
void AutotestPlugin::extensionsInitialized()
{
// Retrieve objects from the plugin manager's object pool
// In the extensionsInitialized function, a plugin can be sure that all
// plugins that depend on it are completely initialized.
}
ExtensionSystem::IPlugin::ShutdownFlag AutotestPlugin::aboutToShutdown()
{
// Save settings
// Disconnect from signals that are not needed during shutdown
// Hide UI (if you add UI that is not in the main window directly)
return SynchronousShutdown;
}
void AutotestPlugin::triggerAction()
{
QMessageBox::information(Core::ICore::mainWindow(),
tr("Action triggered"),
tr("This is an action from 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 AUTOTESTPLUGIN_H
#define AUTOTESTPLUGIN_H
#include "autotest_global.h"
#include <extensionsystem/iplugin.h>
namespace Autotest {
namespace Internal {
class AutotestPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "AutoTest.json")
public:
AutotestPlugin();
~AutotestPlugin();
bool initialize(const QStringList &arguments, QString *errorString);
void extensionsInitialized();
ShutdownFlag aboutToShutdown();
private slots:
void triggerAction();
};
} // namespace Internal
} // namespace Autotest
#endif // AUTOTESTPLUGIN_H
/****************************************************************************
**
** 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 "testcodeparser.h"
#include "testinfo.h"
#include "testtreeitem.h"
#include "testtreemodel.h"
#include "testvisitor.h"
#include <cplusplus/LookupContext.h>
#include <cplusplus/TypeOfExpression.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cppworkingcopy.h>
#include <projectexplorer/session.h>
namespace Autotest {
namespace Internal {
TestCodeParser::TestCodeParser(TestTreeModel *parent)
: QObject(parent),
m_model(parent),
m_currentProject(0)
{
}
void TestCodeParser::updateTestTree()
{
qDebug("updating TestTreeModel");
m_model->beginResetModel();
qDeleteAll(m_cppDocMap);
m_cppDocMap.clear();
QModelIndex autoTestRootIndex = m_model->index(0, 0);
TestTreeItem *autoTestRootItem = static_cast<TestTreeItem *>(autoTestRootIndex.internalPointer());
autoTestRootItem->removeChildren();
m_model->endResetModel();
ProjectExplorer::SessionManager *session = static_cast<ProjectExplorer::SessionManager *>(
ProjectExplorer::SessionManager::instance());
if (!session || !session->hasProjects())
return;
m_currentProject = session->startupProject();
if (!m_currentProject)
return;
scanForTests();
}
/****** scan for QTest related stuff helpers ******/
static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
const CppTools::CppModelManager *cppMM)
{
QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
foreach (CPlusPlus::Document::Include inc, includes) {
// TODO this short cut works only for #include <QtTest>
// bad, as there could be much more different approaches
if (inc.unresolvedFileName() == QLatin1String("QtTest")
&& inc.resolvedFileName().endsWith(QLatin1String("QtTest/QtTest"))) {
return true;
}
}
if (cppMM) {
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
QSet<QString> includes = snapshot.allIncludesForDocument(doc->fileName());
foreach (QString include, includes) {
if (include.endsWith(QLatin1String("QtTest/qtest.h"))) {
return true;
}
}
}
return false;
}
static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM,
const QString &fileName)
{
QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName);
if (parts.size() > 0) {
QByteArray projDefines = parts.at(0)->projectDefines;
return projDefines.contains("#define QT_TESTLIB_LIB 1");
}
return false;
}
static QString testClass(const CPlusPlus::Document::Ptr &doc)
{
static QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};
QString tC;
QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
foreach (CPlusPlus::Document::MacroUse macro, macros) {
if (!macro.isFunctionLike())
continue;
QByteArray name = macro.macro().name();
if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
// TODO high costs....?!
CppTools::WorkingCopy wc = cppMM->workingCopy();
if (wc.contains(doc->fileName())) {
QByteArray src = wc.source(doc->fileName());
tC = QLatin1String(src.mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
} else {
QFile current(doc->fileName());
if (current.exists() && current.open(QFile::ReadOnly)) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
if (current.seek(arg.bytesBegin())) {
QByteArray res = current.read(arg.bytesEnd() - arg.bytesBegin());
if (!res.isEmpty())
tC = QLatin1String(res);
}
current.close();
}
}
break;
}
}
return tC;
}
/****** end of helpers ******/
void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
{
const QString file = doc->fileName();
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
if (includesQtTest(doc, cppMM) && qtTestLibDefined(cppMM, file)) {
QString tc = testClass(doc);
if (tc.isEmpty()) {
// one might have used an approach without macros or defined own macros
QString src = QLatin1String(doc->utf8Source()); // does not work anymore - src is always ""
if (src.contains(QLatin1String("QTest::qExec"))) {
qDebug() << "Src\n===============\n" << src << "\n=============\n";
// TODO extract the class name by using the AST - using regex is too problematic as this
// does not take comments and typedefs or whatever into account....
qDebug() << "Currently not supported approach... (self-defined macro / QTest::qExec() call/...)";
}
} else {
QModelIndex autoTestRootIndex = m_model->index(0, 0);
TestTreeItem *autoTestRootItem = static_cast<TestTreeItem *>(autoTestRootIndex.internalPointer());
TestTreeItem *ttItem = new TestTreeItem(tc, file, TestTreeItem::TEST_CLASS,
autoTestRootItem);
CPlusPlus::TypeOfExpression toe;
toe.init(doc, cppMM->snapshot());
CPlusPlus::Document::Ptr declaringDoc = doc;
QList<CPlusPlus::LookupItem> toeItems = toe(tc.toUtf8(), doc->globalNamespace());
if (toeItems.size()) {
CPlusPlus::Class *toeClass = toeItems.first().declaration()->asClass();
QString declFileName = QLatin1String(toeClass->fileId()->chars(),
toeClass->fileId()->size());
declaringDoc = cppMM->snapshot().document(declFileName);
ttItem->setFilePath(declFileName);
ttItem->setLine(toeClass->line());
ttItem->setColumn(toeClass->column() - 1);
}
if (declaringDoc.isNull())
return;
TestVisitor myVisitor(tc);
myVisitor.accept(declaringDoc->globalNamespace());
QMap<QString, TestCodeLocation> privSlots = myVisitor.privateSlots();
foreach (const QString privS, privSlots.keys()) {
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);
ttItem->appendChild(ttSub);
}
if (m_cppDocMap.contains(file)) {
TestInfo *info = m_cppDocMap[file];
int count = autoTestRootItem->childCount();
for (int i = 0; i < count; ++i) {
TestTreeItem *currentItem = autoTestRootItem->child(i);
if (currentItem->filePath() == file) {
m_model->modifyAutoTestSubtree(i, ttItem);
m_cppDocMap.insert(file, new TestInfo(tc, privSlots.keys(),
doc->revision(),
doc->editorRevision()));
break;
}
}
delete info;
delete ttItem;
} else {
m_model->beginInsertRows(autoTestRootIndex, autoTestRootItem->childCount(), autoTestRootItem->childCount());
autoTestRootItem->appendChild(ttItem);
m_model->endInsertRows();
m_cppDocMap.insert(file, new TestInfo(tc, privSlots.keys(),
doc->revision(), doc->editorRevision()));
}
}
}
}
void TestCodeParser::onDocumentUpdated(CPlusPlus::Document::Ptr doc)
{
if (!m_currentProject)
return;
QString fileName = doc->fileName();
if (!m_cppDocMap.contains(fileName)) {
if (!m_currentProject->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
return;
}
} else {
if (m_cppDocMap[fileName]->revision() == doc->revision()
&& m_cppDocMap[fileName]->editorRevision() == doc->editorRevision()) {
qDebug("Skipped due revision equality"); // added to verify if this ever happens..
return;
}
}
checkDocumentForTestCode(doc);
}
void TestCodeParser::removeFiles(const QStringList &files)
{
foreach (QString file, files) {
if (m_cppDocMap.contains(file)) {
TestInfo *info = m_cppDocMap.value(file);
m_cppDocMap.remove(file);
m_model->removeAutoTestSubtreeByFilePath(file);
delete info;
}
}
}
void TestCodeParser::scanForTests()
{
QStringList list = m_currentProject->files(ProjectExplorer::Project::AllFiles);
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (QString file, list) {
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
checkDocumentForTestCode(doc);
}
}
}
} // 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 TESTCODEPARSER_H
#define TESTCODEPARSER_H
#include <cplusplus/CppDocument.h>
#include <QObject>
#include <QMap>
namespace ProjectExplorer {
class Project;
}
namespace Autotest {
namespace Internal {
class TestInfo;