Add basic support for Qt Quick Test

This commit is contained in:
Christian Stenger
2014-11-06 16:01:06 +01:00
committed by Christian Stenger
parent 85efa2c3c9
commit 0357b0e98b
13 changed files with 681 additions and 90 deletions

View File

@@ -4,10 +4,12 @@ QTC_PLUGIN_DEPENDS += \
coreplugin \
projectexplorer \
cpptools \
qmljstools \
licensechecker
QTC_LIB_DEPENDS += \
cplusplus \
qmljs \
utils
#QTC_PLUGIN_RECOMMENDS += \

View File

@@ -30,6 +30,10 @@
#include <projectexplorer/session.h>
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <utils/textfileformat.h>
namespace Autotest {
@@ -53,6 +57,7 @@ void TestCodeParser::updateTestTree()
clearMaps();
m_model->removeAllAutoTests();
m_model->removeAllQuickTests();
const ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
if (!session || !session->hasProjects())
return;
@@ -88,7 +93,7 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
{
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
foreach (const CPlusPlus::Document::Include inc, includes) {
foreach (const 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")
@@ -100,7 +105,7 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
if (cppMM) {
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
const QSet<QString> allIncludes = snapshot.allIncludesForDocument(doc->fileName());
foreach (const QString include, allIncludes) {
foreach (const QString &include, allIncludes) {
if (include.endsWith(QLatin1String("QtTest/qtest.h"))) {
return true;
}
@@ -109,6 +114,27 @@ static bool includesQtTest(const CPlusPlus::Document::Ptr &doc,
return false;
}
static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
const CppTools::CppModelManager *cppMM)
{
const QList<CPlusPlus::Document::Include> includes = doc->resolvedIncludes();
foreach (const CPlusPlus::Document::Include &inc, includes) {
if (inc.unresolvedFileName() == QLatin1String("QtQuickTest/quicktest.h")
&& inc.resolvedFileName().endsWith(QLatin1String("QtQuickTest/quicktest.h"))) {
return true;
}
}
if (cppMM) {
foreach (const QString &include, cppMM->snapshot().allIncludesForDocument(doc->fileName())) {
if (include.endsWith(QLatin1String("QtQuickTest/quicktest.h")))
return true;
}
}
return false;
}
static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM,
const QString &fileName)
{
@@ -118,24 +144,91 @@ static bool qtTestLibDefined(const CppTools::CppModelManager *cppMM,
return false;
}
static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM,
const QString &fileName)
{
static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR ");
const QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName);
if (parts.size() > 0) {
QByteArray projDefines(parts.at(0)->projectDefines);
foreach (const QByteArray &line, projDefines.split('\n')) {
if (line.contains(qtsd)) {
QByteArray result = line.mid(line.indexOf(qtsd) + qtsd.length());
if (result.startsWith('"'))
result.remove(result.length() - 1, 1).remove(0, 1);
if (result.startsWith("\\\""))
result.remove(result.length() - 2, 2).remove(0, 2);
return QLatin1String(result);
}
}
}
return QString();
}
static QString testClass(const CPlusPlus::Document::Ptr &doc)
{
static QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};
QString tC;
static const QByteArray qtTestMacros[] = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};
const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
foreach (const CPlusPlus::Document::MacroUse macro, macros) {
foreach (const CPlusPlus::Document::MacroUse &macro, macros) {
if (!macro.isFunctionLike())
continue;
const QByteArray name = macro.macro().name();
if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) {
const CPlusPlus::Document::Block arg = macro.arguments().at(0);
return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
}
}
return QString();
}
static QString quickTestName(const CPlusPlus::Document::Ptr &doc)
{
static const QByteArray qtTestMacros[] = {"QUICK_TEST_MAIN", "QUICK_TEST_OPENGL_MAIN"};
const QList<CPlusPlus::Document::MacroUse> macros = doc->macroUses();
foreach (const CPlusPlus::Document::MacroUse &macro, macros) {
if (!macro.isFunctionLike())
continue;
const QByteArray name = macro.macro().name();
if (name == qtTestMacros[0] || name == qtTestMacros[1] || name == qtTestMacros[2]) {
CPlusPlus::Document::Block arg = macro.arguments().at(0);
tC = QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(),
arg.bytesEnd() - arg.bytesBegin()));
break;
return QLatin1String(getFileContent(doc->fileName()).mid(arg.bytesBegin(), arg.bytesEnd() - arg.bytesBegin()));
}
}
return tC;
return QString();
}
static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir)
{
QStringList dirs(srcDir);
QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance();
// make sure even files not listed in pro file are available inside the snapshot
QFutureInterface<void> future;
QmlJS::PathsAndLanguages paths;
paths.maybeInsert(Utils::FileName::fromString(srcDir), QmlJS::Dialect::Qml);
QmlJS::ModelManagerInterface::importScan(future, qmlJsMM->workingCopy(),
paths, qmlJsMM, false, false);
const QmlJS::Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot();
QDirIterator it(srcDir, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo fi(it.fileInfo().canonicalFilePath());
dirs << fi.filePath();
}
QList<QmlJS::Document::Ptr> foundDocs;
foreach (const QString &path, dirs) {
const QList<QmlJS::Document::Ptr> docs = snapshot.documentsInDirectory(path);
foreach (const QmlJS::Document::Ptr &doc, docs) {
const QString fileName(QFileInfo(doc->fileName()).fileName());
if (fileName.startsWith(QLatin1String("tst_")) && fileName.endsWith(QLatin1String(".qml")))
foundDocs << doc;
}
}
return foundDocs;
}
/****** end of helpers ******/
@@ -145,6 +238,11 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
const QString file = doc->fileName();
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
if (includesQtQuickTest(doc, cppMM)) {
handleQtQuickTest(doc);
return;
}
if (includesQtTest(doc, cppMM) && qtTestLibDefined(cppMM, file)) {
QString tc(testClass(doc));
if (tc.isEmpty()) {
@@ -187,7 +285,7 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
TestVisitor myVisitor(tc);
myVisitor.accept(declaringDoc->globalNamespace());
const QMap<QString, TestCodeLocation> privSlots = myVisitor.privateSlots();
foreach (const QString privS, privSlots.keys()) {
foreach (const QString &privS, privSlots.keys()) {
const TestCodeLocation location = privSlots.value(privS);
TestTreeItem *ttSub = new TestTreeItem(privS, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
@@ -253,7 +351,143 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr doc)
}
}
void TestCodeParser::onDocumentUpdated(CPlusPlus::Document::Ptr doc)
void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
{
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
if (quickTestName(doc).isEmpty())
return;
const QString srcDir = quickTestSrcDir(cppMM, doc->fileName());
if (srcDir.isEmpty())
return;
const QList<QmlJS::Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir);
foreach (const QmlJS::Document::Ptr &d, qmlDocs) {
QmlJS::AST::Node *ast = d->ast();
if (!ast) {
qDebug() << "ast is zero pointer" << d->fileName(); // should not happen
continue;
}
TestQmlVisitor qmlVisitor(d);
QmlJS::AST::Node::accept(ast, &qmlVisitor);
const QString tcName = qmlVisitor.testCaseName();
const TestCodeLocation tcLocation = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocation> testFunctions = qmlVisitor.testFunctions();
const QModelIndex quickTestRootIndex = m_model->index(1, 0);
TestTreeItem *quickTestRootItem = static_cast<TestTreeItem *>(quickTestRootIndex.internalPointer());
if (tcName.isEmpty()) {
// if this test case was named before remove it
if (m_quickDocMap.contains(d->fileName())) {
m_model->removeQuickTestSubtreeByFilePath(d->fileName());
m_quickDocMap.remove(d->fileName());
}
bool hadUnnamedTestsBefore;
TestTreeItem *ttItem = m_model->unnamedQuickTests();
if (!ttItem) {
hadUnnamedTestsBefore = false;
ttItem = new TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS,
quickTestRootItem);
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);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
}
} else {
hadUnnamedTestsBefore = true;
// remove unnamed quick tests that are already found for this qml file
m_model->removeUnnamedQuickTest(d->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);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
}
}
TestInfo info = m_quickDocMap.contains(QLatin1String("<unnamed>"))
? m_quickDocMap[QLatin1String("<unnamed>")]
: TestInfo(QString(), QStringList(), 666);
QStringList originalFunctions(info.testFunctions());
foreach (const QString &func, testFunctions.keys()) {
if (!originalFunctions.contains(func))
originalFunctions.append(func);
}
info.setTestFunctions(originalFunctions);
if (hadUnnamedTestsBefore)
m_model->modifyQuickTestSubtree(ttItem->row(), ttItem);
else
m_model->addQuickTest(ttItem);
m_quickDocMap.insert(QLatin1String("<unnamed>"), info);
continue;
} // 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);
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);
ttItem->appendChild(ttSub);
}
// update model and internal map
const QString fileName(tcLocation.m_fileName);
const QmlJS::Document::Ptr qmlDoc =
QmlJSTools::Internal::ModelManager::instance()->snapshot().document(fileName);
if (m_quickDocMap.contains(fileName)) {
for (int i = 0; i < quickTestRootItem->childCount(); ++i) {
if (quickTestRootItem->child(i)->filePath() == fileName) {
m_model->modifyQuickTestSubtree(i, ttItem);
TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision());
ti.setReferencingFile(doc->fileName());
m_quickDocMap.insert(fileName, ti);
break;
}
}
delete ttItem;
} else {
// if it was formerly unnamed remove the respective items
if (m_quickDocMap.contains(QLatin1String("<unnamed>"))) {
m_model->removeUnnamedQuickTest(d->fileName());
TestInfo unnamedInfo = m_quickDocMap[QLatin1String("<unnamed>")];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, testFunctions.keys())
if (functions.contains(func))
functions.removeOne(func);
unnamedInfo.setTestFunctions(functions);
m_quickDocMap.insert(QLatin1String("<unnamed>"), unnamedInfo);
}
m_model->addQuickTest(ttItem);
TestInfo ti(tcName, testFunctions.keys(), 0, qmlDoc->editorRevision());
ti.setReferencingFile(doc->fileName());
m_quickDocMap.insert(tcLocation.m_fileName, ti);
}
}
}
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc)
{
if (!m_currentProject)
return;
@@ -270,13 +504,51 @@ void TestCodeParser::onDocumentUpdated(CPlusPlus::Document::Ptr doc)
checkDocumentForTestCode(doc);
}
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc)
{
if (!m_currentProject)
return;
const QString fileName = doc->fileName();
if (m_quickDocMap.contains(fileName)) {
if ((int)m_quickDocMap[fileName].editorRevision() == doc->editorRevision()) {
qDebug("Skipped due revision equality (QML)"); // added to verify this ever happens....
return;
}
} else if (!m_currentProject->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
// what if the file is not listed inside the pro file, but will be used anyway?
return;
}
const CPlusPlus::Snapshot snapshot = CppTools::CppModelManager::instance()->snapshot();
if (m_quickDocMap.contains(fileName)
&& snapshot.contains(m_quickDocMap[fileName].referencingFile())) {
checkDocumentForTestCode(snapshot.document(m_quickDocMap[fileName].referencingFile()));
}
if (!m_quickDocMap.contains(QLatin1String("<unnamed>")))
return;
// special case of having unnamed TestCases
TestTreeItem *unnamed = m_model->unnamedQuickTests();
for (int row = 0, count = unnamed->childCount(); row < count; ++row) {
const TestTreeItem *child = unnamed->child(row);
if (fileName == child->filePath()) {
if (snapshot.contains(child->mainFile()))
checkDocumentForTestCode(snapshot.document(child->mainFile()));
break;
}
}
}
void TestCodeParser::removeFiles(const QStringList &files)
{
foreach (const QString file, files) {
foreach (const QString &file, files) {
if (m_cppDocMap.contains(file)) {
m_cppDocMap.remove(file);
m_model->removeAutoTestSubtreeByFilePath(file);
}
if (m_quickDocMap.contains(file)) {
m_quickDocMap.remove(file);
m_model->removeQuickTestSubtreeByFilePath(file);
}
}
}
@@ -286,7 +558,7 @@ void TestCodeParser::scanForTests()
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (const QString file, list) {
foreach (const QString &file, list) {
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
checkDocumentForTestCode(doc);
@@ -297,6 +569,7 @@ void TestCodeParser::scanForTests()
void TestCodeParser::clearMaps()
{
m_cppDocMap.clear();
m_quickDocMap.clear();
}
} // namespace Internal

View File

@@ -21,6 +21,8 @@
#include <cplusplus/CppDocument.h>
#include <qmljs/qmljsdocument.h>
#include <QObject>
#include <QMap>
@@ -46,8 +48,10 @@ signals:
public slots:
void updateTestTree();
void checkDocumentForTestCode(CPlusPlus::Document::Ptr doc);
void handleQtQuickTest(CPlusPlus::Document::Ptr doc);
void onDocumentUpdated(CPlusPlus::Document::Ptr doc);
void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &doc);
void onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc);
void removeFiles(const QStringList &files);
private:
@@ -56,6 +60,7 @@ private:
TestTreeModel *m_model;
QMap<QString, TestInfo> m_cppDocMap;
QMap<QString, TestInfo> m_quickDocMap;
ProjectExplorer::Project *m_currentProject;
};

View File

@@ -41,6 +41,26 @@ TestConfiguration::~TestConfiguration()
m_testCases.clear();
}
/**
* @brief sets the test cases for this test configuration.
*
* Watch out for special handling of test configurations, because this method also
* updates the test case count to the current size of \a testCases.
*
* @param testCases list of names of the test functions / test cases
*/
void TestConfiguration::setTestCases(const QStringList &testCases)
{
m_testCases.clear();
m_testCases << testCases;
m_testCaseCount = m_testCases.size();
}
void TestConfiguration::setTestCaseCount(int count)
{
m_testCaseCount = count;
}
void TestConfiguration::setTargetFile(const QString &targetFile)
{
m_targetFile = targetFile;

View File

@@ -40,6 +40,8 @@ public:
int testCaseCount = 0, QObject *parent = 0);
~TestConfiguration();
void setTestCases(const QStringList &testCases);
void setTestCaseCount(int count);
void setTargetFile(const QString &targetFile);
void setTargetName(const QString &targetName);
void setProFile(const QString &proFile);

View File

@@ -224,7 +224,7 @@ static QString which(const QString &path, const QString &cmd)
paths = path.split(QLatin1Char(':'));
#endif
foreach (const QString p, paths) {
foreach (const QString &p, paths) {
const QString fName = p + QDir::separator() + cmd;
QFileInfo fi(fName);
if (fi.exists() && fi.isExecutable())

View File

@@ -94,6 +94,10 @@ bool TestTreeItem::modifyContent(const TestTreeItem *modified)
m_line = modified->m_line;
hasBeenModified = true;
}
if (m_mainFile != modified->m_mainFile) {
m_mainFile = modified->m_mainFile;
hasBeenModified = true;
}
return hasBeenModified;
}

View File

@@ -53,6 +53,8 @@ public:
unsigned line() const { return m_line; }
void setColumn(unsigned column) { m_column = column; }
unsigned column() const { return m_column; }
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; }
Type type() const { return m_type; }
@@ -66,6 +68,7 @@ private:
Type m_type;
unsigned m_line;
unsigned m_column;
QString m_mainFile;
TestTreeItem *m_parent;
QList<TestTreeItem *> m_children;
};

View File

@@ -43,11 +43,11 @@ TestTreeModel::TestTreeModel(QObject *parent) :
QAbstractItemModel(parent),
m_rootItem(new TestTreeItem(QString(), QString(), TestTreeItem::ROOT)),
m_autoTestRootItem(new TestTreeItem(tr("Auto Tests"), QString(), TestTreeItem::ROOT, m_rootItem)),
// m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT, m_rootItem)),
m_quickTestRootItem(new TestTreeItem(tr("Qt Quick Tests"), QString(), TestTreeItem::ROOT, m_rootItem)),
m_parser(new TestCodeParser(this))
{
m_rootItem->appendChild(m_autoTestRootItem);
// m_rootItem->appendChild(m_quickTestRootItem);
m_rootItem->appendChild(m_quickTestRootItem);
m_parser->updateTestTree();
// CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance();
@@ -148,9 +148,11 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole) {
if ((item == m_autoTestRootItem && m_autoTestRootItem->childCount() == 0)
/*|| (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)*/) {
|| (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) {
return QString(item->name() + tr(" (none)"));
} else {
if (item->name().isEmpty())
return tr("<unnamed>");
return item->name();
}
@@ -158,6 +160,10 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
}
switch(role) {
case Qt::ToolTipRole:
if (item->type() == TestTreeItem::TEST_CLASS && item->name().isEmpty())
return tr("<p>Unnamed test cases can't be (un)checked - avoid this by assigning a name."
"<br/>Having unnamed test cases invalidates the check state of named test "
"cases with the same main.cpp when executing selected tests.</p>");
return item->filePath();
case Qt::DecorationRole:
return testTreeIcon(item->type());
@@ -214,8 +220,12 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
TestTreeItem *item = static_cast<TestTreeItem *>(index.internalPointer());
switch(item->type()) {
case TestTreeItem::TEST_CLASS:
if (item->name().isEmpty())
return Qt::ItemIsSelectable | Qt::ItemIsTristate;
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 | Qt::ItemIsUserCheckable;
case TestTreeItem::ROOT:
default:
@@ -245,7 +255,7 @@ bool TestTreeModel::removeRows(int row, int count, const QModelIndex &parent)
bool TestTreeModel::hasTests() const
{
return m_autoTestRootItem->childCount() > 0 /*|| m_quickTestRootItem->childCount() > 0*/;
return m_autoTestRootItem->childCount() > 0 || m_quickTestRootItem->childCount() > 0;
}
static void addProjectInformation(TestConfiguration *config, const QString &filePath)
@@ -265,7 +275,7 @@ static void addProjectInformation(TestConfiguration *config, const QString &file
if (project) {
if (auto target = project->activeTarget()) {
ProjectExplorer::BuildTargetInfoList appTargets = target->applicationTargets();
foreach (ProjectExplorer::BuildTargetInfo bti, appTargets.list) {
foreach (const ProjectExplorer::BuildTargetInfo &bti, appTargets.list) {
if (bti.isValid() && bti.projectFilePath.toString() == proFile) {
targetFile = bti.targetFilePath.toString();
targetName = bti.targetName;
@@ -301,15 +311,44 @@ QList<TestConfiguration *> TestTreeModel::getAllTestCases() const
{
QList<TestConfiguration *> result;
int count = m_autoTestRootItem->childCount();
for (int row = 0; row < count; ++row) {
TestTreeItem *child = m_autoTestRootItem->child(row);
// get all Auto Tests
for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
const TestTreeItem *child = m_autoTestRootItem->child(row);
TestConfiguration *tc = new TestConfiguration(child->name(), QStringList(),
child->childCount());
addProjectInformation(tc, child->filePath());
result << tc;
}
// get all Quick Tests
QMap<QString, int> foundMains;
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
// unnamed Quick Tests must be handled separately
if (child->name().isEmpty()) {
for (int childRow = 0, ccount = child->childCount(); childRow < ccount; ++ childRow) {
const TestTreeItem *grandChild = child->child(childRow);
const QString mainFile = grandChild->mainFile();
foundMains.insert(mainFile, foundMains.contains(mainFile)
? foundMains.value(mainFile) + 1 : 1);
}
continue;
}
// named Quick Test
const QString mainFile = child->mainFile();
foundMains.insert(mainFile, foundMains.contains(mainFile)
? foundMains.value(mainFile) + child->childCount()
: child->childCount());
}
// create TestConfiguration for each main
foreach (const QString &mainFile, foundMains.keys()) {
TestConfiguration *tc = new TestConfiguration(QString(), QStringList(),
foundMains.value(mainFile));
addProjectInformation(tc, mainFile);
result << tc;
}
return result;
}
@@ -318,8 +357,7 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
QList<TestConfiguration *> result;
TestConfiguration *tc;
int count = m_autoTestRootItem->childCount();
for (int row = 0; row < count; ++row) {
for (int row = 0, count = m_autoTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_autoTestRootItem->child(row);
switch (child->checked()) {
@@ -332,7 +370,7 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
continue;
case Qt::PartiallyChecked:
default:
QString childName = child->name();
const QString childName = child->name();
int grandChildCount = child->childCount();
QStringList testCases;
for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
@@ -346,23 +384,186 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
result << tc;
}
}
// Quick Tests must be handled differently - need the calling cpp file to use this in
// addProjectInformation() - additionally this must be unique to not execute the same executable
// on and on and on...
// TODO: do this later on for Auto Tests as well to support strange setups? or redo the model
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);
}
}
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
// unnamed Quick Tests have been handled separately already
if (child->name().isEmpty())
continue;
// named Quick Tests
switch (child->checked()) {
case Qt::Unchecked:
continue;
case Qt::Checked:
case Qt::PartiallyChecked:
default:
QStringList testFunctions;
int grandChildCount = child->childCount();
for (int grandChildRow = 0; grandChildRow < grandChildCount; ++grandChildRow) {
const TestTreeItem *grandChild = child->child(grandChildRow);
if (grandChild->checked() == Qt::Checked)
testFunctions << child->name() + QLatin1String("::") + grandChild->name();
}
TestConfiguration *tc;
if (foundMains.contains(child->mainFile())) {
tc = foundMains[child->mainFile()];
QStringList oldFunctions(tc->testCases());
// if oldFunctions.size() is 0 this test configuration is used for at least one
// unnamed test case
if (oldFunctions.size() == 0) {
tc->setTestCaseCount(tc->testCaseCount() + testFunctions.size());
} else {
oldFunctions << testFunctions;
tc->setTestCases(oldFunctions);
}
} else {
tc = new TestConfiguration(QString(), testFunctions);
addProjectInformation(tc, child->mainFile());
foundMains.insert(child->mainFile(), tc);
}
break;
}
}
foreach (TestConfiguration *config, foundMains.values())
result << config;
return result;
}
TestTreeItem *TestTreeModel::unnamedQuickTests() const
{
for (int row = 0, count = m_quickTestRootItem->childCount(); row < count; ++row) {
TestTreeItem *child = m_quickTestRootItem->child(row);
if (child->name().isEmpty())
return child;
}
return 0;
}
void TestTreeModel::modifyAutoTestSubtree(int row, TestTreeItem *newItem)
{
static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole
<< Qt::ToolTipRole << LinkRole;
QModelIndex toBeModifiedIndex = index(0, 0).child(row, 0);
modifyTestSubtree(toBeModifiedIndex, newItem);
}
void TestTreeModel::removeAutoTestSubtreeByFilePath(const QString &file)
{
const QModelIndex atRootIndex = index(0, 0);
const int count = rowCount(atRootIndex);
for (int row = 0; row < count; ++row) {
const QModelIndex childIndex = atRootIndex.child(row, 0);
TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer());
if (file == childItem->filePath()) {
removeRow(row, atRootIndex);
break;
}
}
emit testTreeModelChanged();
}
void TestTreeModel::addAutoTest(TestTreeItem *newItem)
{
beginInsertRows(index(0, 0), m_autoTestRootItem->childCount(), m_autoTestRootItem->childCount());
m_autoTestRootItem->appendChild(newItem);
endInsertRows();
emit testTreeModelChanged();
}
void TestTreeModel::removeAllAutoTests()
{
beginResetModel();
m_autoTestRootItem->removeChildren();
endResetModel();
emit testTreeModelChanged();
}
void TestTreeModel::modifyQuickTestSubtree(int row, TestTreeItem *newItem)
{
QModelIndex toBeModifiedIndex = index(1, 0).child(row, 0);
modifyTestSubtree(toBeModifiedIndex, newItem);
}
void TestTreeModel::removeQuickTestSubtreeByFilePath(const QString &file)
{
const QModelIndex qtRootIndex = index(1, 0);
for (int row = 0, count = rowCount(qtRootIndex); row < count; ++row) {
const QModelIndex childIndex = qtRootIndex.child(row, 0);
const TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer());
if (file == childItem->filePath()) {
removeRow(row, qtRootIndex);
break;
}
}
emit testTreeModelChanged();
}
void TestTreeModel::addQuickTest(TestTreeItem *newItem)
{
beginInsertRows(index(1, 0), m_quickTestRootItem->childCount(), m_quickTestRootItem->childCount());
m_quickTestRootItem->appendChild(newItem);
endInsertRows();
emit testTreeModelChanged();
}
void TestTreeModel::removeAllQuickTests()
{
beginResetModel();
m_quickTestRootItem->removeChildren();
endResetModel();
emit testTreeModelChanged();
}
void TestTreeModel::removeUnnamedQuickTest(const QString &filePath)
{
TestTreeItem *unnamedQT = unnamedQuickTests();
if (!unnamedQT)
return;
const QModelIndex unnamedQTIndex = index(1, 0).child(unnamedQT->row(), 0);
for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) {
const TestTreeItem *child = unnamedQT->child(childRow);
if (filePath == child->filePath())
removeRow(childRow, unnamedQTIndex);
}
emit testTreeModelChanged();
}
void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeItem *newItem)
{
if (!toBeModifiedIndex.isValid())
return;
static QVector<int> modificationRoles = QVector<int>() << Qt::DisplayRole
<< Qt::ToolTipRole << LinkRole;
TestTreeItem *toBeModifiedItem = static_cast<TestTreeItem *>(toBeModifiedIndex.internalPointer());
if (toBeModifiedItem->modifyContent(newItem))
emit dataChanged(toBeModifiedIndex, toBeModifiedIndex, modificationRoles);
// process sub-items as well...
int childCount = toBeModifiedItem->childCount();
int newChildCount = newItem->childCount();
const int childCount = toBeModifiedItem->childCount();
const int newChildCount = newItem->childCount();
// for keeping the CheckState on modifications
QHash<QString, Qt::CheckState> originalItems;
@@ -427,36 +628,5 @@ void TestTreeModel::modifyAutoTestSubtree(int row, TestTreeItem *newItem)
emit testTreeModelChanged();
}
void TestTreeModel::removeAutoTestSubtreeByFilePath(const QString &file)
{
QModelIndex atRootIndex = index(0, 0);
int count = rowCount(atRootIndex);
for (int row = 0; row < count; ++row) {
QModelIndex childIndex = atRootIndex.child(row, 0);
TestTreeItem *childItem = static_cast<TestTreeItem *>(childIndex.internalPointer());
if (file == childItem->filePath()) {
removeRow(row, atRootIndex);
break;
}
}
emit testTreeModelChanged();
}
void TestTreeModel::addAutoTest(TestTreeItem *newItem)
{
beginInsertRows(index(0, 0), m_autoTestRootItem->childCount(), m_autoTestRootItem->childCount());
m_autoTestRootItem->appendChild(newItem);
endInsertRows();
emit testTreeModelChanged();
}
void TestTreeModel::removeAllAutoTests()
{
beginResetModel();
m_autoTestRootItem->removeChildren();
endResetModel();
emit testTreeModelChanged();
}
} // namespace Internal
} // namespace Autotest

View File

@@ -60,11 +60,19 @@ public:
bool hasTests() const;
QList<TestConfiguration *> getAllTestCases() const;
QList<TestConfiguration *> getSelectedTests() const;
TestTreeItem *unnamedQuickTests() const;
void modifyAutoTestSubtree(int row, TestTreeItem *newItem);
void removeAutoTestSubtreeByFilePath(const QString &file);
void addAutoTest(TestTreeItem *newItem);
void removeAllAutoTests();
void modifyQuickTestSubtree(int row, TestTreeItem *newItem);
void removeQuickTestSubtreeByFilePath(const QString &file);
void addQuickTest(TestTreeItem *newItem);
void removeAllQuickTests();
void removeUnnamedQuickTest(const QString &filePath);
signals:
void testTreeModelChanged();
@@ -72,10 +80,11 @@ public slots:
private:
explicit TestTreeModel(QObject *parent = 0);
void modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeItem *newItem);
TestTreeItem *m_rootItem;
TestTreeItem *m_autoTestRootItem;
// TestTreeItem *m_quickTestRootItem;
TestTreeItem *m_quickTestRootItem;
TestCodeParser *m_parser;
};

View File

@@ -32,6 +32,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <texteditor/texteditor.h>
#include <QToolButton>
@@ -61,11 +63,16 @@ TestTreeViewWidget::TestTreeViewWidget(QWidget *parent) :
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
connect(cppMM, &CppTools::CppModelManager::documentUpdated,
parser, &TestCodeParser::onDocumentUpdated, Qt::QueuedConnection);
parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
connect(cppMM, &CppTools::CppModelManager::aboutToRemoveFiles,
parser, &TestCodeParser::removeFiles, Qt::QueuedConnection);
QmlJS::ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance();
connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
parser, &TestCodeParser::removeFiles, Qt::QueuedConnection);
connect(m_view, &TestTreeView::activated, this, &TestTreeViewWidget::onItemActivated);
}
@@ -208,13 +215,16 @@ void TestTreeView::deselectAll()
void TestTreeView::selectOrDeselectAll(const Qt::CheckState checkState)
{
const TestTreeModel *model = TestTreeModel::instance();
QModelIndex autoTestsIndex = model->index(0, 0, rootIndex());
if (!autoTestsIndex.isValid())
// 2 == Auto Tests and Quick Tests - must be raised if there will be others
for (int rootRow = 0; rootRow < 2; ++rootRow) {
QModelIndex currentRootIndex = model->index(rootRow, 0, rootIndex());
if (!currentRootIndex.isValid())
return;
int count = model->rowCount(autoTestsIndex);
int count = model->rowCount(currentRootIndex);
QModelIndex last;
for (int i = 0; i < count; ++i) {
const QModelIndex classesIndex = model->index(i, 0, autoTestsIndex);
for (int classesRow = 0; classesRow < count; ++classesRow) {
const QModelIndex classesIndex = model->index(classesRow, 0, currentRootIndex);
int funcCount = model->rowCount(classesIndex);
TestTreeItem *item = static_cast<TestTreeItem *>(classesIndex.internalPointer());
if (item) {
@@ -222,14 +232,15 @@ void TestTreeView::selectOrDeselectAll(const Qt::CheckState checkState)
if (!item->childCount())
last = classesIndex;
}
for (int j = 0; j < funcCount; ++j) {
last = model->index(j, 0, classesIndex);
for (int functionRow = 0; functionRow < funcCount; ++functionRow) {
last = model->index(functionRow, 0, classesIndex);
TestTreeItem *item = static_cast<TestTreeItem *>(last.internalPointer());
if (item)
item->setChecked(checkState);
}
}
emit dataChanged(autoTestsIndex, last);
emit dataChanged(currentRootIndex, last);
}
}
} // namespace Internal

View File

@@ -26,11 +26,15 @@
#include <cpptools/cppmodelmanager.h>
#include <qmljs/parser/qmljsast_p.h>
#include <QList>
namespace Autotest {
namespace Internal {
/************************** Cpp Test Symbol Visitor ***************************/
TestVisitor::TestVisitor(const QString &fullQualifiedClassName)
: m_className(fullQualifiedClassName)
{
@@ -59,10 +63,11 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol)
if (className != m_className)
continue;
if (auto func = type->asFunctionType()) {
if (const auto func = type->asFunctionType()) {
if (func->isSlot() && member->isPrivate()) {
const QString name = o.prettyName(func->name());
if (!ignoredFunctions.contains(name) && !name.endsWith(QLatin1String("_data"))) {
// TODO use definition of function instead of declaration!
TestCodeLocation location;
location.m_fileName = QLatin1String(member->fileName());
location.m_line = member->line();
@@ -75,6 +80,8 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol)
return true;
}
/**************************** Cpp Test AST Visitor ****************************/
TestAstVisitor::TestAstVisitor(CPlusPlus::Document::Ptr doc)
: ASTVisitor(doc->translationUnit()),
m_currentDoc(doc)
@@ -90,15 +97,15 @@ bool TestAstVisitor::visit(CPlusPlus::CallAST *ast)
if (!m_currentScope || m_currentDoc.isNull())
return false;
if (auto expressionAST = ast->base_expression) {
if (auto idExpressionAST = expressionAST->asIdExpression()) {
if (auto qualifiedNameAST = idExpressionAST->name->asQualifiedName()) {
if (const auto expressionAST = ast->base_expression) {
if (const auto idExpressionAST = expressionAST->asIdExpression()) {
if (const auto qualifiedNameAST = idExpressionAST->name->asQualifiedName()) {
const CPlusPlus::Overview o;
const QString prettyName = o.prettyName(qualifiedNameAST->name);
if (prettyName == QLatin1String("QTest::qExec")) {
if (auto expressionListAST = ast->expression_list) {
if (const auto expressionListAST = ast->expression_list) {
// first argument is the one we need
if (auto argumentExpressionAST = expressionListAST->value) {
if (const auto argumentExpressionAST = expressionListAST->value) {
CPlusPlus::TypeOfExpression toe;
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
toe.init(m_currentDoc, cppMM->snapshot());
@@ -106,7 +113,7 @@ bool TestAstVisitor::visit(CPlusPlus::CallAST *ast)
= toe(argumentExpressionAST, m_currentDoc, m_currentScope);
if (toeItems.size()) {
if (auto pointerType = toeItems.first().type()->asPointerType())
if (const auto pointerType = toeItems.first().type()->asPointerType())
m_className = o.prettyType(pointerType->elementType());
}
}
@@ -124,5 +131,63 @@ bool TestAstVisitor::visit(CPlusPlus::CompoundStatementAST *ast)
return true;
}
/*************************** Quick Test AST Visitor ***************************/
TestQmlVisitor::TestQmlVisitor(QmlJS::Document::Ptr doc)
: m_currentDoc(doc)
{
}
TestQmlVisitor::~TestQmlVisitor()
{
}
bool TestQmlVisitor::visit(QmlJS::AST::UiObjectDefinition *ast)
{
const QStringRef name = ast->qualifiedTypeNameId->name;
if (name != QLatin1String("TestCase"))
return false;
m_currentTestCaseName.clear();
const auto sourceLocation = ast->firstSourceLocation();
m_testCaseLocation.m_fileName = m_currentDoc->fileName();
m_testCaseLocation.m_line = sourceLocation.startLine;
m_testCaseLocation.m_column = sourceLocation.startColumn - 1;
return true;
}
bool TestQmlVisitor::visit(QmlJS::AST::ExpressionStatement *ast)
{
const QmlJS::AST::ExpressionNode *expr = ast->expression;
return expr->kind == QmlJS::AST::Node::Kind_StringLiteral;
}
bool TestQmlVisitor::visit(QmlJS::AST::UiScriptBinding *ast)
{
const QStringRef name = ast->qualifiedId->name;
return name == QLatin1String("name");
}
bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast)
{
const QStringRef name = ast->name;
if (name.startsWith(QLatin1String("test_"))) {
const auto sourceLocation = ast->firstSourceLocation();
TestCodeLocation location;
location.m_fileName = m_currentDoc->fileName();
location.m_line = sourceLocation.startLine;
location.m_column = sourceLocation.startColumn - 1;
m_testFunctions.insert(name.toString(), location);
}
return false;
}
bool TestQmlVisitor::visit(QmlJS::AST::StringLiteral *ast)
{
m_currentTestCaseName = ast->value.toString();
return false;
}
} // namespace Internal
} // namespace Autotest

View File

@@ -24,6 +24,9 @@
#include <cplusplus/Scope.h>
#include <cplusplus/SymbolVisitor.h>
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <qmljs/qmljsdocument.h>
#include <QMap>
#include <QString>
@@ -69,6 +72,30 @@ private:
};
class TestQmlVisitor : public QmlJS::AST::Visitor
{
public:
TestQmlVisitor(QmlJS::Document::Ptr doc);
virtual ~TestQmlVisitor();
bool visit(QmlJS::AST::UiObjectDefinition *ast);
bool visit(QmlJS::AST::ExpressionStatement *ast);
bool visit(QmlJS::AST::UiScriptBinding *ast);
bool visit(QmlJS::AST::FunctionDeclaration *ast);
bool visit(QmlJS::AST::StringLiteral *ast);
QString testCaseName() const { return m_currentTestCaseName; }
TestCodeLocation testCaseLocation() const { return m_testCaseLocation; }
QMap<QString, TestCodeLocation> testFunctions() const { return m_testFunctions; }
private:
QmlJS::Document::Ptr m_currentDoc;
QString m_currentTestCaseName;
TestCodeLocation m_testCaseLocation;
QMap<QString, TestCodeLocation> m_testFunctions;
};
} // namespace Internal
} // namespace Autotest