Distinguish between parameterized and unparameterized google tests

This additionally fixes an issue when trying to execute a
parameterized google test by invoking 'Run This Test'
or using 'Run Selected Tests'.

Change-Id: I199effcf76b34efd1a041c9a38b5bf90cdc002f8
Reviewed-by: Niels Weber <niels.weber@theqtcompany.com>
This commit is contained in:
Christian Stenger
2016-01-12 16:45:20 +01:00
committed by Niels Weber
parent 2b414536a0
commit cbdf48a792
9 changed files with 80 additions and 37 deletions

View File

@@ -36,6 +36,11 @@ public:
return valid.contains(macro); return valid.contains(macro);
} }
static bool isGTestParameterized(const QString &macro)
{
return macro == QStringLiteral("TEST_P");
}
static bool isQTestMacro(const QByteArray &macro) static bool isQTestMacro(const QByteArray &macro)
{ {
static QByteArrayList valid = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"}; static QByteArrayList valid = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};

View File

@@ -317,9 +317,8 @@ static QString quickTestName(const CPlusPlus::Document::Ptr &doc)
return QString(); return QString();
} }
static QSet<QString> testNames(CPlusPlus::Document::Ptr &document) static bool hasGTestNames(CPlusPlus::Document::Ptr &document)
{ {
QSet<QString> result;
foreach (const CPlusPlus::Document::MacroUse &macro, document->macroUses()) { foreach (const CPlusPlus::Document::MacroUse &macro, document->macroUses()) {
if (!macro.isFunctionLike()) if (!macro.isFunctionLike())
continue; continue;
@@ -327,12 +326,10 @@ static QSet<QString> testNames(CPlusPlus::Document::Ptr &document)
const QVector<CPlusPlus::Document::Block> args = macro.arguments(); const QVector<CPlusPlus::Document::Block> args = macro.arguments();
if (args.size() != 2) if (args.size() != 2)
continue; continue;
const CPlusPlus::Document::Block name = args.first(); return true;
result.insert(QLatin1String(getFileContent(document->fileName())
.mid(name.bytesBegin(), name.bytesEnd() - name.bytesBegin())));
} }
} }
return result; return false;
} }
static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir) static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir)
@@ -457,12 +454,14 @@ static TestTreeItem *constructTestTreeItem(const QString &fileName,
return treeItem; return treeItem;
} }
static TestTreeItem *constructGTestTreeItem(const QString &filePath, const QString &caseName, static TestTreeItem *constructGTestTreeItem(const QString &filePath, const GTestCaseSpec &caseSpec,
const QString &proFile, const QString &proFile,
const TestCodeLocationList &testNames) const TestCodeLocationList &testSets)
{ {
TestTreeItem *item = new TestTreeItem(caseName, QString(), TestTreeItem::GTestCase); TestTreeItem *item = new TestTreeItem(caseSpec.testCaseName, QString(),
foreach (const TestCodeLocationAndType &locationAndType, testNames) { caseSpec.parameterized ? TestTreeItem::GTestCaseParameterized
: TestTreeItem::GTestCase);
foreach (const TestCodeLocationAndType &locationAndType, testSets) {
TestTreeItem *treeItemChild = new TestTreeItem(locationAndType.m_name, filePath, TestTreeItem *treeItemChild = new TestTreeItem(locationAndType.m_name, filePath,
locationAndType.m_type); locationAndType.m_type);
treeItemChild->setLine(locationAndType.m_line); treeItemChild->setLine(locationAndType.m_line);
@@ -549,9 +548,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document)
} }
if (includesGTest(document, modelManager)) { if (includesGTest(document, modelManager)) {
const QSet<QString> &names = testNames(document); if (hasGTestNames(document)) {
if (!names.isEmpty()) { handleGTest(document->fileName());
handleGTest(document->fileName(), names);
return; return;
} }
} }
@@ -609,7 +607,7 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document)
} }
} }
void TestCodeParser::handleGTest(const QString &filePath, const QSet<QString> &names) void TestCodeParser::handleGTest(const QString &filePath)
{ {
const QByteArray &fileContent = getFileContent(filePath); const QByteArray &fileContent = getFileContent(filePath);
const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot(); const CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot();
@@ -619,8 +617,7 @@ void TestCodeParser::handleGTest(const QString &filePath, const QSet<QString> &n
GTestVisitor visitor(document); GTestVisitor visitor(document);
visitor.accept(ast); visitor.accept(ast);
QMap<QString, TestCodeLocationList> result = visitor.gtestFunctions(); QMap<GTestCaseSpec, TestCodeLocationList> result = visitor.gtestFunctions();
QTC_CHECK(names.contains(result.keys().toSet()));
updateGTests(document, result); updateGTests(document, result);
} }
@@ -1018,7 +1015,7 @@ void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
} }
void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc, void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc,
const QMap<QString, TestCodeLocationList> &tests) const QMap<GTestCaseSpec, TestCodeLocationList> &tests)
{ {
const QString &fileName = doc->fileName(); const QString &fileName = doc->fileName();
removeGTestsByName(fileName); removeGTestsByName(fileName);
@@ -1029,12 +1026,12 @@ void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc,
if (ppList.size()) if (ppList.size())
proFile = ppList.at(0)->projectFile; proFile = ppList.at(0)->projectFile;
foreach (const QString &testName, tests.keys()) { foreach (const GTestCaseSpec &testSpec, tests.keys()) {
TestTreeItem *item = constructGTestTreeItem(fileName, testName, proFile, tests.value(testName)); TestTreeItem *item = constructGTestTreeItem(fileName, testSpec, proFile, tests.value(testSpec));
TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision()); TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision());
info.setProFile(proFile); info.setProFile(proFile);
foreach (const TestCodeLocationAndType &testSet, tests.value(testName)) { foreach (const TestCodeLocationAndType &testSet, tests.value(testSpec)) {
GTestInfo gtestInfo(testName, testSet.m_name, fileName); GTestInfo gtestInfo(testSpec.testCaseName, testSet.m_name, fileName);
if (testSet.m_type == TestTreeItem::GTestNameDisabled) if (testSet.m_type == TestTreeItem::GTestNameDisabled)
gtestInfo.setEnabled(false); gtestInfo.setEnabled(false);
m_gtestDocList.append(gtestInfo); m_gtestDocList.append(gtestInfo);

View File

@@ -41,6 +41,7 @@ struct TestCodeLocationAndType;
class TestInfo; class TestInfo;
class UnnamedQuickTestInfo; class UnnamedQuickTestInfo;
class GTestInfo; class GTestInfo;
struct GTestCaseSpec;
class TestCodeParser : public QObject class TestCodeParser : public QObject
{ {
@@ -84,7 +85,7 @@ public slots:
void updateTestTree(); void updateTestTree();
void checkDocumentForTestCode(CPlusPlus::Document::Ptr document); void checkDocumentForTestCode(CPlusPlus::Document::Ptr document);
void handleQtQuickTest(CPlusPlus::Document::Ptr document); void handleQtQuickTest(CPlusPlus::Document::Ptr document);
void handleGTest(const QString &filePath, const QSet<QString> &names); void handleGTest(const QString &filePath);
void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document); void onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document);
void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document); void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document);
@@ -109,7 +110,7 @@ private:
void updateModelAndQuickDocMap(QmlJS::Document::Ptr document, void updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
const QString &referencingFile, TestTreeItem *testItem); const QString &referencingFile, TestTreeItem *testItem);
void updateGTests(const CPlusPlus::Document::Ptr &doc, void updateGTests(const CPlusPlus::Document::Ptr &doc,
const QMap<QString, TestCodeLocationList> &tests); const QMap<GTestCaseSpec, TestCodeLocationList> &tests);
void removeUnnamedQuickTestsByName(const QString &fileName); void removeUnnamedQuickTestsByName(const QString &fileName);
void removeGTestsByName(const QString &fileName); void removeGTestsByName(const QString &fileName);

View File

@@ -108,6 +108,7 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
|| (type == TestTreeItem::TestClass && index.data().toString() != unnamed) || (type == TestTreeItem::TestClass && index.data().toString() != unnamed)
|| (type == TestTreeItem::TestDataTag) || (type == TestTreeItem::TestDataTag)
|| (type == TestTreeItem::GTestCase) || (type == TestTreeItem::GTestCase)
|| (type == TestTreeItem::GTestCaseParameterized)
|| (type == TestTreeItem::GTestName)) { || (type == TestTreeItem::GTestName)) {
runThisTest = new QAction(tr("Run This Test"), &menu); runThisTest = new QAction(tr("Run This Test"), &menu);
runThisTest->setEnabled(enabled); runThisTest->setEnabled(enabled);
@@ -255,6 +256,7 @@ void TestNavigationWidget::onRunThisTestTriggered()
if (item->type() == TestTreeItem::TestClass || item->type() == TestTreeItem::TestFunction if (item->type() == TestTreeItem::TestClass || item->type() == TestTreeItem::TestFunction
|| item->type() == TestTreeItem::TestDataTag || item->type() == TestTreeItem::TestDataTag
|| item->type() == TestTreeItem::GTestCase || item->type() == TestTreeItem::GTestCase
|| item->type() == TestTreeItem::GTestCaseParameterized
|| item->type() == TestTreeItem::GTestName) { || item->type() == TestTreeItem::GTestName) {
if (TestConfiguration *configuration = m_model->getTestConfiguration(item)) { if (TestConfiguration *configuration = m_model->getTestConfiguration(item)) {
TestRunner *runner = TestRunner::instance(); TestRunner *runner = TestRunner::instance();

View File

@@ -40,6 +40,7 @@ TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type ty
case TestClass: case TestClass:
case TestFunction: case TestFunction:
case GTestCase: case GTestCase:
case GTestCaseParameterized:
case GTestName: case GTestName:
case GTestNameDisabled: case GTestNameDisabled:
m_checked = Qt::Checked; m_checked = Qt::Checked;
@@ -76,7 +77,7 @@ static QIcon testTreeIcon(TestTreeItem::Type type)
QIcon(QLatin1String(":/images/func.png")), QIcon(QLatin1String(":/images/func.png")),
QIcon(QLatin1String(":/images/data.png")) QIcon(QLatin1String(":/images/data.png"))
}; };
if (type == TestTreeItem::GTestCase) if (type == TestTreeItem::GTestCase || type == TestTreeItem::GTestCaseParameterized)
return icons[1]; return icons[1];
if (int(type) >= int(sizeof icons / sizeof *icons)) if (int(type) >= int(sizeof icons / sizeof *icons))
@@ -111,6 +112,7 @@ QVariant TestTreeItem::data(int /*column*/, int role) const
return QVariant(); return QVariant();
case TestClass: case TestClass:
case GTestCase: case GTestCase:
case GTestCaseParameterized:
return m_name.isEmpty() ? QVariant() : checked(); return m_name.isEmpty() ? QVariant() : checked();
case TestFunction: case TestFunction:
case GTestName: case GTestName:
@@ -191,7 +193,8 @@ void TestTreeItem::setChecked(const Qt::CheckState checkState)
break; break;
} }
case TestClass: case TestClass:
case GTestCase: { case GTestCase:
case GTestCaseParameterized: {
Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked); Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
for (int row = 0, count = childCount(); row < count; ++row) for (int row = 0, count = childCount(); row < count; ++row)
childItem(row)->setChecked(usedState); childItem(row)->setChecked(usedState);
@@ -208,6 +211,7 @@ Qt::CheckState TestTreeItem::checked() const
case TestClass: case TestClass:
case TestFunction: case TestFunction:
case GTestCase: case GTestCase:
case GTestCaseParameterized:
case GTestName: case GTestName:
case GTestNameDisabled: case GTestNameDisabled:
return m_checked; return m_checked;

View File

@@ -48,7 +48,8 @@ public:
TestDataTag, TestDataTag,
TestDataFunction, TestDataFunction,
TestSpecialFunction, TestSpecialFunction,
GTestCase, // should we distinguish between Case and Fixture? GTestCase,
GTestCaseParameterized,
GTestName, GTestName,
GTestNameDisabled GTestNameDisabled
}; };

View File

@@ -140,6 +140,7 @@ bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int
switch (item->type()) { switch (item->type()) {
case TestTreeItem::TestClass: case TestTreeItem::TestClass:
case TestTreeItem::GTestCase: case TestTreeItem::GTestCase:
case TestTreeItem::GTestCaseParameterized:
if (item->childCount() > 0) if (item->childCount() > 0)
emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0)); emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
break; break;
@@ -166,6 +167,7 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
switch(item->type()) { switch(item->type()) {
case TestTreeItem::TestClass: case TestTreeItem::TestClass:
case TestTreeItem::GTestCase: case TestTreeItem::GTestCase:
case TestTreeItem::GTestCaseParameterized:
if (item->name().isEmpty()) if (item->name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsTristate | Qt::ItemIsUserCheckable;
@@ -389,8 +391,14 @@ QList<TestConfiguration *> TestTreeModel::getSelectedTests() const
const TestTreeItem *grandChild = child->childItem(grandChildRow); const TestTreeItem *grandChild = child->childItem(grandChildRow);
const QString &proFile = grandChild->mainFile(); const QString &proFile = grandChild->mainFile();
QStringList enabled = proFilesWithEnabledTestSets.value(proFile); QStringList enabled = proFilesWithEnabledTestSets.value(proFile);
if (grandChild->checked() == Qt::Checked) if (grandChild->checked() == Qt::Checked) {
enabled << child->name() + QLatin1Char('.') + grandChild->name(); QString testSpecifier = child->name() + QLatin1Char('.') + grandChild->name();
if (child->type() == TestTreeItem::GTestCaseParameterized) {
testSpecifier.prepend(QLatin1String("*/"));
testSpecifier.append(QLatin1String("/*"));
}
enabled << testSpecifier;
}
proFilesWithEnabledTestSets.insert(proFile, enabled); proFilesWithEnabledTestSets.insert(proFile, enabled);
} }
} }
@@ -461,10 +469,14 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item)
config->setProject(project); config->setProject(project);
break; break;
} }
case TestTreeItem::GTestCase: { case TestTreeItem::GTestCase:
case TestTreeItem::GTestCaseParameterized: {
QString testSpecifier = item->name() + QLatin1String(".*");
if (item->type() == TestTreeItem::GTestCaseParameterized)
testSpecifier.prepend(QLatin1String("*/"));
if (int childCount = item->childCount()) { if (int childCount = item->childCount()) {
config = new TestConfiguration(QString(), config = new TestConfiguration(QString(), QStringList(testSpecifier));
QStringList(item->name() + QLatin1String(".*")));
config->setTestCaseCount(childCount); config->setTestCaseCount(childCount);
config->setProFile(item->childItem(0)->mainFile()); config->setProFile(item->childItem(0)->mainFile());
config->setProject(project); config->setProject(project);
@@ -474,8 +486,13 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item)
} }
case TestTreeItem::GTestName: { case TestTreeItem::GTestName: {
const TestTreeItem *parent = item->parentItem(); const TestTreeItem *parent = item->parentItem();
config = new TestConfiguration(QString(), QString testSpecifier = parent->name() + QLatin1Char('.') + item->name();
QStringList(parent->name() + QLatin1Char('.') + item->name()));
if (parent->type() == TestTreeItem::GTestCaseParameterized) {
testSpecifier.prepend(QLatin1String("*/"));
testSpecifier.append(QLatin1String("/*"));
}
config = new TestConfiguration(QString(), QStringList(testSpecifier));
config->setProFile(item->mainFile()); config->setProFile(item->mainFile());
config->setProject(project); config->setProject(project);
config->setTestType(TestTypeGTest); config->setTestType(TestTypeGTest);
@@ -578,7 +595,7 @@ void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type
TestTreeItem *toBeUpdated = 0; TestTreeItem *toBeUpdated = 0;
for (int row = 0, count = parent->childCount(); row < count; ++row) { for (int row = 0, count = parent->childCount(); row < count; ++row) {
TestTreeItem *current = parent->childItem(row); TestTreeItem *current = parent->childItem(row);
if (current->name() == item->name()) { if (current->name() == item->name() && current->type() == item->type()) {
toBeUpdated = current; toBeUpdated = current;
break; break;
} }

View File

@@ -380,7 +380,10 @@ bool GTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast)
locationAndType.m_column = column - 1; locationAndType.m_column = column - 1;
locationAndType.m_type = disabled ? TestTreeItem::GTestNameDisabled locationAndType.m_type = disabled ? TestTreeItem::GTestNameDisabled
: TestTreeItem::GTestName; : TestTreeItem::GTestName;
m_gtestFunctions[testCaseName].append(locationAndType); GTestCaseSpec spec;
spec.testCaseName = testCaseName;
spec.parameterized = TestUtils::isGTestParameterized(prettyName);
m_gtestFunctions[spec].append(locationAndType);
} }
return false; return false;

View File

@@ -123,18 +123,31 @@ private:
}; };
struct GTestCaseSpec
{
QString testCaseName;
bool parameterized;
};
inline bool operator<(const GTestCaseSpec &spec1, const GTestCaseSpec &spec2)
{
if (spec1.testCaseName != spec2.testCaseName)
return spec1.testCaseName < spec2.testCaseName;
return !spec1.parameterized;
}
class GTestVisitor : public CPlusPlus::ASTVisitor class GTestVisitor : public CPlusPlus::ASTVisitor
{ {
public: public:
GTestVisitor(CPlusPlus::Document::Ptr doc); GTestVisitor(CPlusPlus::Document::Ptr doc);
bool visit(CPlusPlus::FunctionDefinitionAST *ast); bool visit(CPlusPlus::FunctionDefinitionAST *ast);
QMap<QString, TestCodeLocationList> gtestFunctions() const { return m_gtestFunctions; } QMap<GTestCaseSpec, TestCodeLocationList> gtestFunctions() const { return m_gtestFunctions; }
private: private:
CPlusPlus::Document::Ptr m_document; CPlusPlus::Document::Ptr m_document;
CPlusPlus::Overview m_overview; CPlusPlus::Overview m_overview;
QMap<QString, TestCodeLocationList> m_gtestFunctions; QMap<GTestCaseSpec, TestCodeLocationList> m_gtestFunctions;
}; };