Commit cbdf48a7 authored by Christian Stenger's avatar Christian Stenger Committed by Niels Weber
Browse files

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: default avatarNiels Weber <niels.weber@theqtcompany.com>
parent 2b414536
......@@ -36,6 +36,11 @@ public:
return valid.contains(macro);
}
static bool isGTestParameterized(const QString &macro)
{
return macro == QStringLiteral("TEST_P");
}
static bool isQTestMacro(const QByteArray &macro)
{
static QByteArrayList valid = {"QTEST_MAIN", "QTEST_APPLESS_MAIN", "QTEST_GUILESS_MAIN"};
......
......@@ -317,9 +317,8 @@ static QString quickTestName(const CPlusPlus::Document::Ptr &doc)
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()) {
if (!macro.isFunctionLike())
continue;
......@@ -327,12 +326,10 @@ static QSet<QString> testNames(CPlusPlus::Document::Ptr &document)
const QVector<CPlusPlus::Document::Block> args = macro.arguments();
if (args.size() != 2)
continue;
const CPlusPlus::Document::Block name = args.first();
result.insert(QLatin1String(getFileContent(document->fileName())
.mid(name.bytesBegin(), name.bytesEnd() - name.bytesBegin())));
return true;
}
}
return result;
return false;
}
static QList<QmlJS::Document::Ptr> scanDirectoryForQuickTestQmlFiles(const QString &srcDir)
......@@ -457,12 +454,14 @@ static TestTreeItem *constructTestTreeItem(const QString &fileName,
return treeItem;
}
static TestTreeItem *constructGTestTreeItem(const QString &filePath, const QString &caseName,
static TestTreeItem *constructGTestTreeItem(const QString &filePath, const GTestCaseSpec &caseSpec,
const QString &proFile,
const TestCodeLocationList &testNames)
const TestCodeLocationList &testSets)
{
TestTreeItem *item = new TestTreeItem(caseName, QString(), TestTreeItem::GTestCase);
foreach (const TestCodeLocationAndType &locationAndType, testNames) {
TestTreeItem *item = new TestTreeItem(caseSpec.testCaseName, QString(),
caseSpec.parameterized ? TestTreeItem::GTestCaseParameterized
: TestTreeItem::GTestCase);
foreach (const TestCodeLocationAndType &locationAndType, testSets) {
TestTreeItem *treeItemChild = new TestTreeItem(locationAndType.m_name, filePath,
locationAndType.m_type);
treeItemChild->setLine(locationAndType.m_line);
......@@ -549,9 +548,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document)
}
if (includesGTest(document, modelManager)) {
const QSet<QString> &names = testNames(document);
if (!names.isEmpty()) {
handleGTest(document->fileName(), names);
if (hasGTestNames(document)) {
handleGTest(document->fileName());
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 CPlusPlus::Snapshot snapshot = CPlusPlus::CppModelManagerBase::instance()->snapshot();
......@@ -619,8 +617,7 @@ void TestCodeParser::handleGTest(const QString &filePath, const QSet<QString> &n
GTestVisitor visitor(document);
visitor.accept(ast);
QMap<QString, TestCodeLocationList> result = visitor.gtestFunctions();
QTC_CHECK(names.contains(result.keys().toSet()));
QMap<GTestCaseSpec, TestCodeLocationList> result = visitor.gtestFunctions();
updateGTests(document, result);
}
......@@ -1018,7 +1015,7 @@ void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
}
void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc,
const QMap<QString, TestCodeLocationList> &tests)
const QMap<GTestCaseSpec, TestCodeLocationList> &tests)
{
const QString &fileName = doc->fileName();
removeGTestsByName(fileName);
......@@ -1029,12 +1026,12 @@ void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc,
if (ppList.size())
proFile = ppList.at(0)->projectFile;
foreach (const QString &testName, tests.keys()) {
TestTreeItem *item = constructGTestTreeItem(fileName, testName, proFile, tests.value(testName));
foreach (const GTestCaseSpec &testSpec, tests.keys()) {
TestTreeItem *item = constructGTestTreeItem(fileName, testSpec, proFile, tests.value(testSpec));
TestInfo info(item->name(), item->getChildNames(), doc->revision(), doc->editorRevision());
info.setProFile(proFile);
foreach (const TestCodeLocationAndType &testSet, tests.value(testName)) {
GTestInfo gtestInfo(testName, testSet.m_name, fileName);
foreach (const TestCodeLocationAndType &testSet, tests.value(testSpec)) {
GTestInfo gtestInfo(testSpec.testCaseName, testSet.m_name, fileName);
if (testSet.m_type == TestTreeItem::GTestNameDisabled)
gtestInfo.setEnabled(false);
m_gtestDocList.append(gtestInfo);
......
......@@ -41,6 +41,7 @@ struct TestCodeLocationAndType;
class TestInfo;
class UnnamedQuickTestInfo;
class GTestInfo;
struct GTestCaseSpec;
class TestCodeParser : public QObject
{
......@@ -84,7 +85,7 @@ public slots:
void updateTestTree();
void checkDocumentForTestCode(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 onQmlDocumentUpdated(const QmlJS::Document::Ptr &document);
......@@ -109,7 +110,7 @@ private:
void updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
const QString &referencingFile, TestTreeItem *testItem);
void updateGTests(const CPlusPlus::Document::Ptr &doc,
const QMap<QString, TestCodeLocationList> &tests);
const QMap<GTestCaseSpec, TestCodeLocationList> &tests);
void removeUnnamedQuickTestsByName(const QString &fileName);
void removeGTestsByName(const QString &fileName);
......
......@@ -108,6 +108,7 @@ void TestNavigationWidget::contextMenuEvent(QContextMenuEvent *event)
|| (type == TestTreeItem::TestClass && index.data().toString() != unnamed)
|| (type == TestTreeItem::TestDataTag)
|| (type == TestTreeItem::GTestCase)
|| (type == TestTreeItem::GTestCaseParameterized)
|| (type == TestTreeItem::GTestName)) {
runThisTest = new QAction(tr("Run This Test"), &menu);
runThisTest->setEnabled(enabled);
......@@ -255,6 +256,7 @@ void TestNavigationWidget::onRunThisTestTriggered()
if (item->type() == TestTreeItem::TestClass || item->type() == TestTreeItem::TestFunction
|| item->type() == TestTreeItem::TestDataTag
|| item->type() == TestTreeItem::GTestCase
|| item->type() == TestTreeItem::GTestCaseParameterized
|| item->type() == TestTreeItem::GTestName) {
if (TestConfiguration *configuration = m_model->getTestConfiguration(item)) {
TestRunner *runner = TestRunner::instance();
......
......@@ -40,6 +40,7 @@ TestTreeItem::TestTreeItem(const QString &name, const QString &filePath, Type ty
case TestClass:
case TestFunction:
case GTestCase:
case GTestCaseParameterized:
case GTestName:
case GTestNameDisabled:
m_checked = Qt::Checked;
......@@ -76,7 +77,7 @@ static QIcon testTreeIcon(TestTreeItem::Type type)
QIcon(QLatin1String(":/images/func.png")),
QIcon(QLatin1String(":/images/data.png"))
};
if (type == TestTreeItem::GTestCase)
if (type == TestTreeItem::GTestCase || type == TestTreeItem::GTestCaseParameterized)
return icons[1];
if (int(type) >= int(sizeof icons / sizeof *icons))
......@@ -111,6 +112,7 @@ QVariant TestTreeItem::data(int /*column*/, int role) const
return QVariant();
case TestClass:
case GTestCase:
case GTestCaseParameterized:
return m_name.isEmpty() ? QVariant() : checked();
case TestFunction:
case GTestName:
......@@ -191,7 +193,8 @@ void TestTreeItem::setChecked(const Qt::CheckState checkState)
break;
}
case TestClass:
case GTestCase: {
case GTestCase:
case GTestCaseParameterized: {
Qt::CheckState usedState = (checkState == Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
for (int row = 0, count = childCount(); row < count; ++row)
childItem(row)->setChecked(usedState);
......@@ -208,6 +211,7 @@ Qt::CheckState TestTreeItem::checked() const
case TestClass:
case TestFunction:
case GTestCase:
case GTestCaseParameterized:
case GTestName:
case GTestNameDisabled:
return m_checked;
......
......@@ -48,7 +48,8 @@ public:
TestDataTag,
TestDataFunction,
TestSpecialFunction,
GTestCase, // should we distinguish between Case and Fixture?
GTestCase,
GTestCaseParameterized,
GTestName,
GTestNameDisabled
};
......
......@@ -140,6 +140,7 @@ bool TestTreeModel::setData(const QModelIndex &index, const QVariant &value, int
switch (item->type()) {
case TestTreeItem::TestClass:
case TestTreeItem::GTestCase:
case TestTreeItem::GTestCaseParameterized:
if (item->childCount() > 0)
emit dataChanged(index.child(0, 0), index.child(item->childCount() - 1, 0));
break;
......@@ -166,6 +167,7 @@ Qt::ItemFlags TestTreeModel::flags(const QModelIndex &index) const
switch(item->type()) {
case TestTreeItem::TestClass:
case TestTreeItem::GTestCase:
case TestTreeItem::GTestCaseParameterized:
if (item->name().isEmpty())
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
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 QString &proFile = grandChild->mainFile();
QStringList enabled = proFilesWithEnabledTestSets.value(proFile);
if (grandChild->checked() == Qt::Checked)
enabled << child->name() + QLatin1Char('.') + grandChild->name();
if (grandChild->checked() == Qt::Checked) {
QString testSpecifier = child->name() + QLatin1Char('.') + grandChild->name();
if (child->type() == TestTreeItem::GTestCaseParameterized) {
testSpecifier.prepend(QLatin1String("*/"));
testSpecifier.append(QLatin1String("/*"));
}
enabled << testSpecifier;
}
proFilesWithEnabledTestSets.insert(proFile, enabled);
}
}
......@@ -461,10 +469,14 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item)
config->setProject(project);
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()) {
config = new TestConfiguration(QString(),
QStringList(item->name() + QLatin1String(".*")));
config = new TestConfiguration(QString(), QStringList(testSpecifier));
config->setTestCaseCount(childCount);
config->setProFile(item->childItem(0)->mainFile());
config->setProject(project);
......@@ -474,8 +486,13 @@ TestConfiguration *TestTreeModel::getTestConfiguration(const TestTreeItem *item)
}
case TestTreeItem::GTestName: {
const TestTreeItem *parent = item->parentItem();
config = new TestConfiguration(QString(),
QStringList(parent->name() + QLatin1Char('.') + item->name()));
QString testSpecifier = 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->setProject(project);
config->setTestType(TestTypeGTest);
......@@ -578,7 +595,7 @@ void TestTreeModel::addTestTreeItem(TestTreeItem *item, TestTreeModel::Type type
TestTreeItem *toBeUpdated = 0;
for (int row = 0, count = parent->childCount(); row < count; ++row) {
TestTreeItem *current = parent->childItem(row);
if (current->name() == item->name()) {
if (current->name() == item->name() && current->type() == item->type()) {
toBeUpdated = current;
break;
}
......
......@@ -380,7 +380,10 @@ bool GTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast)
locationAndType.m_column = column - 1;
locationAndType.m_type = disabled ? TestTreeItem::GTestNameDisabled
: TestTreeItem::GTestName;
m_gtestFunctions[testCaseName].append(locationAndType);
GTestCaseSpec spec;
spec.testCaseName = testCaseName;
spec.parameterized = TestUtils::isGTestParameterized(prettyName);
m_gtestFunctions[spec].append(locationAndType);
}
return false;
......
......@@ -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
{
public:
GTestVisitor(CPlusPlus::Document::Ptr doc);
bool visit(CPlusPlus::FunctionDefinitionAST *ast);
QMap<QString, TestCodeLocationList> gtestFunctions() const { return m_gtestFunctions; }
QMap<GTestCaseSpec, TestCodeLocationList> gtestFunctions() const { return m_gtestFunctions; }
private:
CPlusPlus::Document::Ptr m_document;
CPlusPlus::Overview m_overview;
QMap<QString, TestCodeLocationList> m_gtestFunctions;
QMap<GTestCaseSpec, TestCodeLocationList> m_gtestFunctions;
};
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment