forked from qt-creator/qt-creator
Add basic support for Qt Quick Test
This commit is contained in:
committed by
Christian Stenger
parent
85efa2c3c9
commit
0357b0e98b
@@ -4,10 +4,12 @@ QTC_PLUGIN_DEPENDS += \
|
||||
coreplugin \
|
||||
projectexplorer \
|
||||
cpptools \
|
||||
qmljstools \
|
||||
licensechecker
|
||||
|
||||
QTC_LIB_DEPENDS += \
|
||||
cplusplus \
|
||||
qmljs \
|
||||
utils
|
||||
|
||||
#QTC_PLUGIN_RECOMMENDS += \
|
||||
|
||||
@@ -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 ¯o, 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 ¯o, 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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user