summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/autotest/autotest.qrc1
-rw-r--r--plugins/autotest/images/data.pngbin0 -> 646 bytes
-rw-r--r--plugins/autotest/testcodeparser.cpp54
-rw-r--r--plugins/autotest/testtreeitem.h5
-rw-r--r--plugins/autotest/testtreemodel.cpp25
-rw-r--r--plugins/autotest/testvisitor.cpp137
-rw-r--r--plugins/autotest/testvisitor.h27
7 files changed, 234 insertions, 15 deletions
diff --git a/plugins/autotest/autotest.qrc b/plugins/autotest/autotest.qrc
index 2770aff5fe..8c70e3ae3f 100644
--- a/plugins/autotest/autotest.qrc
+++ b/plugins/autotest/autotest.qrc
@@ -21,5 +21,6 @@
<file>images/run.png</file>
<file>images/runselected.png</file>
<file>images/stop.png</file>
+ <file>images/data.png</file>
</qresource>
</RCC>
diff --git a/plugins/autotest/images/data.png b/plugins/autotest/images/data.png
new file mode 100644
index 0000000000..1f510c4042
--- /dev/null
+++ b/plugins/autotest/images/data.png
Binary files differ
diff --git a/plugins/autotest/testcodeparser.cpp b/plugins/autotest/testcodeparser.cpp
index d01fb3140f..b7a13b4ee8 100644
--- a/plugins/autotest/testcodeparser.cpp
+++ b/plugins/autotest/testcodeparser.cpp
@@ -355,11 +355,40 @@ static CPlusPlus::Document::Ptr declaringDocument(CPlusPlus::Document::Ptr doc,
return declaringDoc;
}
+static bool hasFunctionWithDataTagUsage(const QMap<QString, TestCodeLocationAndType> &testFunctions)
+{
+ foreach (const QString &functionName, testFunctions.keys()) {
+ if (functionName.endsWith(QLatin1String("_data")) &&
+ testFunctions.contains(functionName.left(functionName.size() - 5))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static QMap<QString, TestCodeLocationList> checkForDataTags(const QString &fileName,
+ const QMap<QString, TestCodeLocationAndType> &testFunctions)
+{
+ if (hasFunctionWithDataTagUsage(testFunctions)) {
+ const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot();
+ const QByteArray fileContent = getFileContent(fileName);
+ CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, fileName);
+ document->check();
+ CPlusPlus::AST *ast = document->translationUnit()->ast();
+ TestDataFunctionVisitor visitor(document);
+ visitor.accept(ast);
+ return visitor.dataTags();
+ }
+ return QMap<QString, TestCodeLocationList>();
+}
+
+
static TestTreeItem constructTestTreeItem(const QString &fileName,
const QString &mainFile, // used for Quick Tests only
const QString &testCaseName,
int line, int column,
- const QMap<QString, TestCodeLocationAndType> functions)
+ const QMap<QString, TestCodeLocationAndType> functions,
+ const QMap<QString, TestCodeLocationList> dataTags = QMap<QString, TestCodeLocationList>())
{
TestTreeItem treeItem(testCaseName, fileName, TestTreeItem::TEST_CLASS);
treeItem.setMainFile(mainFile); // used for Quick Tests only
@@ -368,10 +397,24 @@ static TestTreeItem constructTestTreeItem(const QString &fileName,
foreach (const QString &functionName, functions.keys()) {
const TestCodeLocationAndType locationAndType = functions.value(functionName);
- TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_fileName,
+ TestTreeItem *treeItemChild = new TestTreeItem(functionName, locationAndType.m_name,
locationAndType.m_type, &treeItem);
treeItemChild->setLine(locationAndType.m_line);
treeItemChild->setColumn(locationAndType.m_column);
+ // check for data tags and if there are any for this function add them
+ const QString qualifiedFunctionName = testCaseName + QLatin1String("::") + functionName;
+ if (dataTags.contains(qualifiedFunctionName)) {
+ const TestCodeLocationList &tags = dataTags.value(qualifiedFunctionName);
+ foreach (const TestCodeLocationAndType &tagLocation, tags) {
+ TestTreeItem *tagTreeItem = new TestTreeItem(tagLocation.m_name,
+ locationAndType.m_name,
+ tagLocation.m_type, treeItemChild);
+ tagTreeItem->setLine(tagLocation.m_line);
+ tagTreeItem->setColumn(tagLocation.m_column);
+ treeItemChild->appendChild(tagTreeItem);
+ }
+ }
+
treeItem.appendChild(treeItemChild);
}
return treeItem;
@@ -437,8 +480,11 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document)
visitor.accept(declaringDoc->globalNamespace());
const QMap<QString, TestCodeLocationAndType> testFunctions = visitor.privateSlots();
+ const QMap<QString, TestCodeLocationList> dataTags =
+ checkForDataTags(declaringDoc->fileName(), testFunctions);
TestTreeItem item = constructTestTreeItem(declaringDoc->fileName(), QString(),
- testCaseName, line, column, testFunctions);
+ testCaseName, line, column, testFunctions,
+ dataTags);
updateModelAndCppDocMap(document, declaringDoc->fileName(), item);
return;
}
@@ -487,7 +533,7 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document)
// construct new/modified TestTreeItem
TestTreeItem testTreeItem
- = constructTestTreeItem(tcLocationAndType.m_fileName, cppFileName, testCaseName,
+ = constructTestTreeItem(tcLocationAndType.m_name, cppFileName, testCaseName,
tcLocationAndType.m_line, tcLocationAndType.m_column,
testFunctions);
diff --git a/plugins/autotest/testtreeitem.h b/plugins/autotest/testtreeitem.h
index 0cfa1aead1..8c7bf14307 100644
--- a/plugins/autotest/testtreeitem.h
+++ b/plugins/autotest/testtreeitem.h
@@ -35,6 +35,7 @@ public:
ROOT,
TEST_CLASS,
TEST_FUNCTION,
+ TEST_DATATAG,
TEST_DATAFUNCTION,
TEST_SPECIALFUNCTION
};
@@ -84,12 +85,14 @@ private:
};
struct TestCodeLocationAndType {
- QString m_fileName;
+ QString m_name; // tag name for m_type == TEST_DATATAG, file name for other values
unsigned m_line;
unsigned m_column;
TestTreeItem::Type m_type;
};
+typedef QVector<TestCodeLocationAndType> TestCodeLocationList;
+
} // namespace Internal
} // namespace Autotest
diff --git a/plugins/autotest/testtreemodel.cpp b/plugins/autotest/testtreemodel.cpp
index 5eea7b7d98..e30b7f651c 100644
--- a/plugins/autotest/testtreemodel.cpp
+++ b/plugins/autotest/testtreemodel.cpp
@@ -180,12 +180,13 @@ int TestTreeModel::columnCount(const QModelIndex &) const
static QIcon testTreeIcon(TestTreeItem::Type type)
{
- static QIcon icons[3] = {
+ static QIcon icons[] = {
QIcon(),
QIcon(QLatin1String(":/images/class.png")),
- QIcon(QLatin1String(":/images/func.png"))
+ QIcon(QLatin1String(":/images/func.png")),
+ QIcon(QLatin1String(":/images/data.png"))
};
- if (type >= 3)
+ if (type >= sizeof(icons) / sizeof(icons[0]))
return icons[2];
return icons[type];
}
@@ -204,7 +205,7 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
|| (item == m_quickTestRootItem && m_quickTestRootItem->childCount() == 0)) {
return QString(item->name() + tr(" (none)"));
} else {
- if (item->name().isEmpty())
+ if (item->name().isEmpty() && item->type() == TestTreeItem::TEST_CLASS)
return tr(Constants::UNNAMED_QUICKTESTS);
return item->name();
}
@@ -222,6 +223,7 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
case Qt::CheckStateRole:
switch (item->type()) {
case TestTreeItem::ROOT:
+ case TestTreeItem::TEST_DATATAG:
case TestTreeItem::TEST_DATAFUNCTION:
case TestTreeItem::TEST_SPECIALFUNCTION:
return QVariant();
@@ -682,7 +684,7 @@ void TestTreeModel::updateUnnamedQuickTest(const QString &fileName, const QStrin
foreach (const QString &functionName, functions.keys()) {
const TestCodeLocationAndType locationAndType = functions.value(functionName);
- TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_fileName,
+ TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name,
locationAndType.m_type, &unnamed);
testFunction->setLine(locationAndType.m_line);
testFunction->setColumn(locationAndType.m_column);
@@ -819,6 +821,19 @@ void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem
TestTreeItem *modifiedChild = newItem.child(row);
if (toBeModifiedChild->modifyContent(modifiedChild))
emit dataChanged(child, child, modificationRoles);
+
+ // handle data tags - just remove old and add them
+ if (modifiedChild->childCount() || toBeModifiedChild->childCount()) {
+ beginRemoveRows(child, 0, toBeModifiedChild->childCount());
+ toBeModifiedChild->removeChildren();
+ endRemoveRows();
+ const int count = modifiedChild->childCount();
+ beginInsertRows(child, 0, count);
+ for (int childRow = 0; childRow < count; ++childRow)
+ toBeModifiedChild->appendChild(new TestTreeItem(*modifiedChild->child(childRow)));
+ endInsertRows();
+ }
+
if (checkStates.contains(toBeModifiedChild->name())) {
Qt::CheckState state = checkStates.value(toBeModifiedChild->name());
if (state != toBeModifiedChild->checked()) {
diff --git a/plugins/autotest/testvisitor.cpp b/plugins/autotest/testvisitor.cpp
index d0179c1ca7..3fe84cb077 100644
--- a/plugins/autotest/testvisitor.cpp
+++ b/plugins/autotest/testvisitor.cpp
@@ -21,7 +21,6 @@
#include <cplusplus/FullySpecifiedType.h>
#include <cplusplus/LookupContext.h>
-#include <cplusplus/Overview.h>
#include <cplusplus/Symbols.h>
#include <cplusplus/TypeOfExpression.h>
@@ -29,6 +28,8 @@
#include <qmljs/parser/qmljsast_p.h>
+#include <utils/qtcassert.h>
+
#include <QList>
namespace Autotest {
@@ -73,11 +74,11 @@ bool TestVisitor::visit(CPlusPlus::Class *symbol)
CPlusPlus::Function *functionDefinition = m_symbolFinder.findMatchingDefinition(
func, CppTools::CppModelManager::instance()->snapshot(), true);
if (functionDefinition) {
- locationAndType.m_fileName = QString::fromUtf8(functionDefinition->fileName());
+ locationAndType.m_name = QString::fromUtf8(functionDefinition->fileName());
locationAndType.m_line = functionDefinition->line();
locationAndType.m_column = functionDefinition->column() - 1;
} else { // if we cannot find the definition use declaration as fallback
- locationAndType.m_fileName = QString::fromUtf8(member->fileName());
+ locationAndType.m_name = QString::fromUtf8(member->fileName());
locationAndType.m_line = member->line();
locationAndType.m_column = member->column() - 1;
}
@@ -145,6 +146,132 @@ bool TestAstVisitor::visit(CPlusPlus::CompoundStatementAST *ast)
return true;
}
+/********************** Test Data Function AST Visitor ************************/
+
+TestDataFunctionVisitor::TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc)
+ : CPlusPlus::ASTVisitor(doc->translationUnit()),
+ m_currentDoc(doc),
+ m_currentAstDepth(0),
+ m_insideUsingQTestDepth(0),
+ m_insideUsingQTest(false)
+{
+}
+
+TestDataFunctionVisitor::~TestDataFunctionVisitor()
+{
+}
+
+bool TestDataFunctionVisitor::visit(CPlusPlus::UsingDirectiveAST *ast)
+{
+ if (auto nameAST = ast->name) {
+ if (m_overview.prettyName(nameAST->name) == QLatin1String("QTest")) {
+ m_insideUsingQTest = true;
+ // we need the surrounding AST depth as using directive is an AST itself
+ m_insideUsingQTestDepth = m_currentAstDepth - 1;
+ }
+ }
+ return true;
+}
+
+bool TestDataFunctionVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast)
+{
+ if (ast->declarator) {
+ CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId();
+ if (!id)
+ return false;
+
+ const QString prettyName = m_overview.prettyName(id->name->name);
+ // do not handle functions that aren't real test data functions
+ if (!prettyName.endsWith(QLatin1String("_data")) || !ast->symbol
+ || ast->symbol->argumentCount() != 0) {
+ return false;
+ }
+
+ m_currentFunction = prettyName.left(prettyName.size() - 5);
+ m_currentTags.clear();
+ return true;
+ }
+
+ return false;
+}
+
+bool TestDataFunctionVisitor::visit(CPlusPlus::CallAST *ast)
+{
+ if (m_currentFunction.isEmpty())
+ return true;
+
+ unsigned firstToken;
+ if (newRowCallFound(ast, &firstToken)) {
+ if (const auto expressionListAST = ast->expression_list) {
+ // first argument is the one we need
+ if (const auto argumentExpressionAST = expressionListAST->value) {
+ if (const auto stringLiteral = argumentExpressionAST->asStringLiteral()) {
+ auto token = m_currentDoc->translationUnit()->tokenAt(
+ stringLiteral->literal_token);
+ if (token.isStringLiteral()) {
+ unsigned line = 0;
+ unsigned column = 0;
+ m_currentDoc->translationUnit()->getTokenStartPosition(
+ firstToken, &line, &column);
+ TestCodeLocationAndType locationAndType;
+ locationAndType.m_name = QString::fromUtf8(token.spell());
+ locationAndType.m_column = column - 1;
+ locationAndType.m_line = line;
+ locationAndType.m_type = TestTreeItem::TEST_DATATAG;
+ m_currentTags.append(locationAndType);
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool TestDataFunctionVisitor::preVisit(CPlusPlus::AST *)
+{
+ ++m_currentAstDepth;
+ return true;
+}
+
+void TestDataFunctionVisitor::postVisit(CPlusPlus::AST *ast)
+{
+ --m_currentAstDepth;
+ m_insideUsingQTest &= m_currentAstDepth >= m_insideUsingQTestDepth;
+
+ if (!ast->asFunctionDefinition())
+ return;
+
+ if (!m_currentFunction.isEmpty() && !m_currentTags.isEmpty())
+ m_dataTags.insert(m_currentFunction, m_currentTags);
+
+ m_currentFunction.clear();
+ m_currentTags.clear();
+}
+
+bool TestDataFunctionVisitor::newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const
+{
+ QTC_ASSERT(firstToken, return false);
+
+ if (!ast->base_expression)
+ return false;
+
+ bool found = false;
+
+ if (const CPlusPlus::IdExpressionAST *exp = ast->base_expression->asIdExpression()) {
+ if (!exp->name)
+ return false;
+
+ if (const auto qualifiedNameAST = exp->name->asQualifiedName()) {
+ found = m_overview.prettyName(qualifiedNameAST->name) == QLatin1String("QTest::newRow");
+ *firstToken = qualifiedNameAST->firstToken();
+ } else if (m_insideUsingQTest) {
+ found = m_overview.prettyName(exp->name->name) == QLatin1String("newRow");
+ *firstToken = exp->name->firstToken();
+ }
+ }
+ return found;
+}
+
/*************************** Quick Test AST Visitor ***************************/
TestQmlVisitor::TestQmlVisitor(QmlJS::Document::Ptr doc)
@@ -164,7 +291,7 @@ bool TestQmlVisitor::visit(QmlJS::AST::UiObjectDefinition *ast)
m_currentTestCaseName.clear();
const auto sourceLocation = ast->firstSourceLocation();
- m_testCaseLocation.m_fileName = m_currentDoc->fileName();
+ m_testCaseLocation.m_name = m_currentDoc->fileName();
m_testCaseLocation.m_line = sourceLocation.startLine;
m_testCaseLocation.m_column = sourceLocation.startColumn - 1;
m_testCaseLocation.m_type = TestTreeItem::TEST_CLASS;
@@ -192,7 +319,7 @@ bool TestQmlVisitor::visit(QmlJS::AST::FunctionDeclaration *ast)
|| specialFunctions.contains(name.toString())) {
const auto sourceLocation = ast->firstSourceLocation();
TestCodeLocationAndType locationAndType;
- locationAndType.m_fileName = m_currentDoc->fileName();
+ locationAndType.m_name = m_currentDoc->fileName();
locationAndType.m_line = sourceLocation.startLine;
locationAndType.m_column = sourceLocation.startColumn - 1;
if (specialFunctions.contains(name.toString()))
diff --git a/plugins/autotest/testvisitor.h b/plugins/autotest/testvisitor.h
index 07d5835293..4441eb26f8 100644
--- a/plugins/autotest/testvisitor.h
+++ b/plugins/autotest/testvisitor.h
@@ -24,6 +24,7 @@
#include <cplusplus/ASTVisitor.h>
#include <cplusplus/CppDocument.h>
+#include <cplusplus/Overview.h>
#include <cplusplus/Scope.h>
#include <cplusplus/SymbolVisitor.h>
@@ -72,6 +73,32 @@ private:
};
+class TestDataFunctionVisitor : public CPlusPlus::ASTVisitor
+{
+public:
+ TestDataFunctionVisitor(CPlusPlus::Document::Ptr doc);
+ virtual ~TestDataFunctionVisitor();
+
+ bool visit(CPlusPlus::UsingDirectiveAST *ast);
+ bool visit(CPlusPlus::FunctionDefinitionAST *ast);
+ bool visit(CPlusPlus::CallAST *ast);
+ bool preVisit(CPlusPlus::AST *ast);
+ void postVisit(CPlusPlus::AST *ast);
+ QMap<QString, TestCodeLocationList> dataTags() const { return m_dataTags; }
+
+private:
+ bool newRowCallFound(CPlusPlus::CallAST *ast, unsigned *firstToken) const;
+
+ CPlusPlus::Document::Ptr m_currentDoc;
+ CPlusPlus::Overview m_overview;
+ QString m_currentFunction;
+ QMap<QString, TestCodeLocationList> m_dataTags;
+ TestCodeLocationList m_currentTags;
+ unsigned m_currentAstDepth;
+ unsigned m_insideUsingQTestDepth;
+ bool m_insideUsingQTest;
+};
+
class TestQmlVisitor : public QmlJS::AST::Visitor
{
public: