Commit 213a687f authored by Christian Stenger's avatar Christian Stenger
Browse files

Rework parsing and provide unit test



Handling of parsing has slightly changed. The parser now uses simple
states to be capable of postponing triggered parses if there is already
a parsing in progress. Furthermore the parser now waits for the current
project to be completely scanned.

Change-Id: I6d4968d28194ba8d23d3a0ee6ab454d81a549e67
Reviewed-by: default avatarAndre Poenitz <andre.poenitz@theqtcompany.com>
parent 751d2e2b
......@@ -56,3 +56,8 @@ RESOURCES += \
FORMS += \
testsettingspage.ui
equals(TEST, 1) {
HEADERS += autotestunittests.h
SOURCES += autotestunittests.cpp
RESOURCES += autotestunittests.qrc
}
......@@ -44,6 +44,10 @@
#include <QtPlugin>
#ifdef WITH_TESTS
#include "autotestunittests.h"
#endif
using namespace Autotest::Internal;
static AutotestPlugin *m_instance = 0;
......@@ -151,3 +155,11 @@ void AutotestPlugin::triggerAction()
tr("This is an action from Autotest."));
}
QList<QObject *> AutotestPlugin::createTestObjects() const
{
QList<QObject *> tests;
#ifdef WITH_TESTS
tests << new AutoTestUnitTests(TestTreeModel::instance());
#endif
return tests;
}
......@@ -52,6 +52,7 @@ private slots:
private:
bool checkLicense();
void initializeMenuEntries();
QList<QObject *> createTestObjects() const;
const QSharedPointer<TestSettings> m_settings;
};
......
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** This file is part of the Qt Creator Enterprise Auto Test Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
**
****************************************************************************/
#include "autotestconstants.h"
#include "autotestunittests.h"
#include "testcodeparser.h"
#include "testtreemodel.h"
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cpptoolstestcase.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/toolchain.h>
#include <QSignalSpy>
#include <QTest>
#include <QTime>
#include <coreplugin/navigationwidget.h>
#include <qtsupport/qtkitinformation.h>
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
namespace Autotest {
namespace Internal {
AutoTestUnitTests::AutoTestUnitTests(TestTreeModel *model, QObject *parent)
: QObject(parent),
m_model(model),
m_tmpDir(0),
m_isQt4(false)
{
}
void AutoTestUnitTests::initTestCase()
{
const QList<Kit *> allKits = KitManager::kits();
if (allKits.count() != 1)
QSKIP("This test requires exactly one kit to be present");
if (auto qtVersion = QtSupport::QtKitInformation::qtVersion(allKits.first()))
m_isQt4 = qtVersion->qtVersionString().startsWith(QLatin1Char('4'));
else
QSKIP("Could not figure out which Qt version is used for default kit.");
const ToolChain * const toolchain = ToolChainKitInformation::toolChain(allKits.first());
if (!toolchain)
QSKIP("This test requires that there is a kit with a toolchain.");
m_tmpDir = new CppTools::Tests::TemporaryCopiedDir(QLatin1String(":/unit_test"));
}
void AutoTestUnitTests::cleanupTestCase()
{
delete m_tmpDir;
}
void AutoTestUnitTests::testCodeParser()
{
QFETCH(QString, projectFilePath);
QFETCH(int, expectedAutoTestsCount);
QFETCH(int, expectedNamedQuickTestsCount);
QFETCH(int, expectedUnnamedQuickTestsCount);
NavigationWidget *navigation = NavigationWidget::instance();
navigation->activateSubWidget(Constants::AUTOTEST_ID);
CppTools::Tests::ProjectOpenerAndCloser projectManager;
const CppTools::ProjectInfo projectInfo = projectManager.open(projectFilePath, true);
QVERIFY(projectInfo.isValid());
QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished()));
QVERIFY(parserSpy.wait(20000));
if (m_isQt4)
expectedNamedQuickTestsCount = expectedUnnamedQuickTestsCount = 0;
QCOMPARE(m_model->autoTestsCount(), expectedAutoTestsCount);
QCOMPARE(m_model->namedQuickTestsCount(), expectedNamedQuickTestsCount);
QCOMPARE(m_model->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount);
QCOMPARE(m_model->parser()->autoTestsCount(), expectedAutoTestsCount);
QCOMPARE(m_model->parser()->namedQuickTestsCount(), expectedNamedQuickTestsCount);
QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount);
}
void AutoTestUnitTests::testCodeParser_data()
{
QTest::addColumn<QString>("projectFilePath");
QTest::addColumn<int>("expectedAutoTestsCount");
QTest::addColumn<int>("expectedNamedQuickTestsCount");
QTest::addColumn<int>("expectedUnnamedQuickTestsCount");
QTest::newRow("plainAutoTest")
<< QString(m_tmpDir->path() + QLatin1String("/plain/plain.pro"))
<< 1 << 0 << 0;
QTest::newRow("mixedAutoTestAndQuickTests")
<< QString(m_tmpDir->path() + QLatin1String("/mixed_atp/mixed_atp.pro"))
<< 3 << 5 << 3;
}
} // namespace Internal
} // namespace Autotest
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc
** All rights reserved.
** For any questions to Digia, please use contact form at http://qt.digia.com
**
** This file is part of the Qt Creator Enterprise Auto Test Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.digia.com
**
****************************************************************************/
#ifndef AUTOTESTUNITTESTS_H
#define AUTOTESTUNITTESTS_H
#include <QObject>
#include <QTemporaryDir>
namespace CppTools { namespace Tests { class TemporaryCopiedDir; } }
namespace Autotest {
namespace Internal {
class TestTreeModel;
class AutoTestUnitTests : public QObject
{
Q_OBJECT
public:
explicit AutoTestUnitTests(TestTreeModel *model, QObject *parent = 0);
signals:
private slots:
void initTestCase();
void cleanupTestCase();
void testCodeParser();
void testCodeParser_data();
private:
TestTreeModel *m_model;
CppTools::Tests::TemporaryCopiedDir *m_tmpDir;
bool m_isQt4;
};
} // namespace Internal
} // namespace Autotest
#endif // AUTOTESTUNITTESTS_H
<RCC>
<qresource prefix="/">
<file>unit_test/mixed_atp/src/main.cpp</file>
<file>unit_test/mixed_atp/tests/auto/bench/tst_benchtest.cpp</file>
<file>unit_test/mixed_atp/tests/auto/dummy/tst_foo.cpp</file>
<file>unit_test/mixed_atp/tests/auto/dummy/tst_foo.h</file>
<file>unit_test/mixed_atp/tests/auto/gui/tst_guitest.cpp</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/bar/tst_foo.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/notlisted/tst_bla.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/main.cpp</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/TestDummy.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/tst_test1.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/tst_test2.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/tst_test3.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto2/main.cpp</file>
<file>unit_test/mixed_atp/tests/auto/quickauto2/tst_test1.qml</file>
<file>unit_test/mixed_atp/tests/auto/quickauto2/tst_test2.qml</file>
<file>unit_test/plain/test_plain/tst_simple.cpp</file>
<file>unit_test/plain/test_plain/tst_simple.h</file>
<file>unit_test/plain/plain.pro</file>
<file>unit_test/mixed_atp/mixed_atp.pro</file>
<file>unit_test/plain/test_plain/test_plain.pro</file>
<file>unit_test/mixed_atp/tests/tests.pro</file>
<file>unit_test/mixed_atp/src/src.pro</file>
<file>unit_test/mixed_atp/tests/auto/bench/bench.pro</file>
<file>unit_test/mixed_atp/tests/auto/dummy/dummy.pro</file>
<file>unit_test/mixed_atp/tests/auto/gui/gui.pro</file>
<file>unit_test/mixed_atp/tests/auto/quickauto/quickauto.pro</file>
<file>unit_test/mixed_atp/tests/auto/quickauto2/quickauto2.pro</file>
<file>unit_test/mixed_atp/tests/auto/auto.pro</file>
</qresource>
</RCC>
......@@ -49,7 +49,10 @@ TestCodeParser::TestCodeParser(TestTreeModel *parent)
: QObject(parent),
m_model(parent),
m_parserEnabled(true),
m_pendingUpdate(false)
m_pendingUpdate(false),
m_fullUpdatePostPoned(false),
m_partialUpdatePostPoned(false),
m_parserState(Idle)
{
// connect to ProgressManager to post-pone test parsing when CppModelManager is parsing
auto progressManager = qobject_cast<Core::ProgressManager *>(Core::ProgressManager::instance());
......@@ -57,6 +60,8 @@ TestCodeParser::TestCodeParser(TestTreeModel *parent)
this, &TestCodeParser::onTaskStarted);
connect(progressManager, &Core::ProgressManager::allTasksFinished,
this, &TestCodeParser::onAllTasksFinished);
connect(this, &TestCodeParser::partialParsingFinished,
this, &TestCodeParser::onPartialParsingFinished);
}
TestCodeParser::~TestCodeParser()
......@@ -81,25 +86,26 @@ void TestCodeParser::updateTestTree()
{
if (!m_parserEnabled) {
m_pendingUpdate = true;
qDebug() << "Skipped update due to running parser or pro file evaluate";
return;
}
qDebug("updating TestTreeModel");
clearMaps();
emit cacheCleared();
if (ProjectExplorer::Project *project = currentProject()) {
if (auto qmakeProject = qobject_cast<QmakeProjectManager::QmakeProject *>(project)) {
if (qmakeProject->asyncUpdateState() != QmakeProjectManager::QmakeProject::Base) {
m_pendingUpdate = true;
return;
}
connect(qmakeProject, &QmakeProjectManager::QmakeProject::proFilesEvaluated,
this, &TestCodeParser::onProFileEvaluated, Qt::UniqueConnection);
}
} else
return;
scanForTests();
m_pendingUpdate = false;
clearMaps();
emit cacheCleared();
scanForTests();
}
/****** scan for QTest related stuff helpers ******/
......@@ -443,7 +449,7 @@ void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &docume
} else if (!project->files(ProjectExplorer::Project::AllFiles).contains(fileName)) {
return;
}
checkDocumentForTestCode(document);
scanForTests(QStringList(fileName));
}
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
......@@ -464,15 +470,17 @@ void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
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[fileName].referencingFile().isEmpty())
scanForTests(QStringList(m_quickDocMap[fileName].referencingFile()));
}
if (!m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)))
return;
// special case of having unnamed TestCases
const QString &mainFile = m_model->getMainFileForUnnamedQuickTest(fileName);
if (!mainFile.isEmpty() && snapshot.contains(mainFile))
checkDocumentForTestCode(snapshot.document(mainFile));
if (!mainFile.isEmpty() && snapshot.contains(mainFile)) {
scanForTests(QStringList(mainFile));
}
}
void TestCodeParser::removeFiles(const QStringList &files)
......@@ -481,13 +489,62 @@ void TestCodeParser::removeFiles(const QStringList &files)
removeTestsIfNecessary(file);
}
bool TestCodeParser::postponed(const QStringList &fileList)
{
switch (m_parserState) {
case Idle:
return false;
case PartialParse:
// partial is running, postponing a full parse
if (fileList.isEmpty()) {
m_partialUpdatePostPoned = false;
m_postPonedFiles.clear();
m_fullUpdatePostPoned = true;
} else {
// partial parse triggered, but full parse is postponed already, ignoring this
if (m_fullUpdatePostPoned)
return true;
// partial parse triggered, postpone or add current files to already postponed partial
foreach (const QString &file, fileList)
m_postPonedFiles.insert(file);
m_partialUpdatePostPoned = true;
}
return true;
case FullParse:
// full parse is running, postponing another full parse
if (fileList.isEmpty()) {
m_partialUpdatePostPoned = false;
m_postPonedFiles.clear();
m_fullUpdatePostPoned = true;
} else {
// full parse already postponed, ignoring triggering a partial parse
if (m_fullUpdatePostPoned) {
return true;
}
// partial parse triggered, postpone or add current files to already postponed partial
foreach (const QString &file, fileList)
m_postPonedFiles.insert(file);
m_partialUpdatePostPoned = true;
}
return true;
}
QTC_ASSERT(false, return false); // should not happen at all
}
void TestCodeParser::scanForTests(const QStringList &fileList)
{
if (postponed(fileList))
return;
QStringList list;
if (fileList.isEmpty()) {
list = currentProject()->files(ProjectExplorer::Project::AllFiles);
if (list.isEmpty())
return;
m_parserState = FullParse;
} else {
list << fileList;
m_parserState = PartialParse;
}
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
......@@ -499,6 +556,18 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
checkDocumentForTestCode(doc);
}
}
switch (m_parserState) {
case PartialParse:
m_parserState = Idle;
emit partialParsingFinished();
break;
case FullParse:
m_parserState = Idle;
emit parsingFinished();
break;
case Idle:
break;
}
}
void TestCodeParser::clearMaps()
......@@ -588,22 +657,39 @@ void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile)
void TestCodeParser::onTaskStarted(Core::Id type)
{
if (type != CppTools::Constants::TASK_INDEX
&& type != QmakeProjectManager::Constants::PROFILE_EVALUATE)
if (type != CppTools::Constants::TASK_INDEX)
return;
m_parserEnabled = false;
}
void TestCodeParser::onAllTasksFinished(Core::Id type)
{
if (type != CppTools::Constants::TASK_INDEX
&& type != QmakeProjectManager::Constants::PROFILE_EVALUATE)
// only CPP parsing is relevant as we trigger Qml parsing internally anyway
if (type != CppTools::Constants::TASK_INDEX)
return;
m_parserEnabled = true;
if (m_pendingUpdate)
updateTestTree();
}
void TestCodeParser::onPartialParsingFinished()
{
QTC_ASSERT(m_fullUpdatePostPoned != m_partialUpdatePostPoned
|| ((m_fullUpdatePostPoned || m_partialUpdatePostPoned) == false),
m_partialUpdatePostPoned = false;m_postPonedFiles.clear(););
if (m_fullUpdatePostPoned) {
m_fullUpdatePostPoned = false;
updateTestTree();
} else if (m_partialUpdatePostPoned) {
m_partialUpdatePostPoned = false;
QStringList tmp;
foreach (const QString &file, m_postPonedFiles)
tmp << file;
m_postPonedFiles.clear();
scanForTests(tmp);
}
}
void TestCodeParser::updateUnnamedQuickTests(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions)
{
......@@ -710,5 +796,31 @@ void TestCodeParser::onProFileEvaluated()
}
}
#ifdef WITH_TESTS
int TestCodeParser::autoTestsCount() const
{
int count = 0;
foreach (const QString &file, m_cppDocMap.keys()) {
if (m_cppDocMap.value(file).referencingFile().isEmpty())
++count;
}
return count;
}
int TestCodeParser::namedQuickTestsCount() const
{
if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)))
return m_quickDocMap.size() - 1;
return m_quickDocMap.size();
}
int TestCodeParser::unnamedQuickTestsCount() const
{
if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)))
return m_quickDocMap.value(tr(Constants::UNNAMED_QUICKTESTS)).testFunctions().size();
return 0;
}
#endif
} // namespace Internal
} // namespace Autotest
......@@ -43,9 +43,21 @@ class TestCodeParser : public QObject
{
Q_OBJECT
public:
enum State {
Idle,
PartialParse,
FullParse
};
explicit TestCodeParser(TestTreeModel *parent = 0);
virtual ~TestCodeParser();
#ifdef WITH_TESTS
int autoTestsCount() const;
int namedQuickTestsCount() const;
int unnamedQuickTestsCount() const;
#endif
signals:
void cacheCleared();
void testItemCreated(const TestTreeItem &item, TestTreeModel::Type type);
......@@ -55,6 +67,8 @@ signals:
void unnamedQuickTestsUpdated(const QString &filePath, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions);
void unnamedQuickTestsRemoved(const QString &filePath);
void parsingFinished();
void partialParsingFinished();
public slots:
void emitUpdateTestTree();
......@@ -68,6 +82,7 @@ public slots:
void onProFileEvaluated();
private:
bool postponed(const QStringList &fileList);
void scanForTests(const QStringList &fileList = QStringList());
void clearMaps();
void removeTestsIfNecessary(const QString &fileName);
......@@ -75,6 +90,7 @@ private:
void onTaskStarted(Core::Id type);
void onAllTasksFinished(Core::Id type);
void onPartialParsingFinished();
void updateUnnamedQuickTests(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions);
void updateModelAndCppDocMap(CPlusPlus::Document::Ptr document,
......@@ -87,6 +103,10 @@ private:
QMap<QString, TestInfo> m_quickDocMap;
bool m_parserEnabled;
bool m_pendingUpdate;
bool m_fullUpdatePostPoned;
bool m_partialUpdatePostPoned;
QSet<QString> m_postPonedFiles;
State m_parserState;
};
} // namespace Internal
......
......@@ -61,8 +61,6 @@ TestTreeModel::TestTreeModel(QObject *parent) :
connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved,
this, &TestTreeModel::removeUnnamedQuickTests);
m_parser->updateTestTree();
// CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance();
// if (cppMM) {
// // replace later on by
......@@ -808,6 +806,27 @@ void TestTreeModel::processChildren(QModelIndex &parentIndex, const TestTreeItem
}
}
#ifdef WITH_TESTS
int TestTreeModel::autoTestsCount() const
{
return m_autoTestRootItem ? m_autoTestRootItem->childCount() : 0;
}
int TestTreeModel::namedQuickTestsCount() const
{
return m_quickTestRootItem
? m_quickTestRootItem->childCount() - (hasUnnamedQuickTests() ? 1 : 0)
: 0;
}
int TestTreeModel::unnamedQuickTestsCount() const
{
if (TestTreeItem *unnamed = unnamedQuickTests())
return unnamed->childCount();
return 0;
}
#endif
/***************************** Sort/Filter Model **********************************/
TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent)
......
......@@ -75,6 +75,11 @@ public:
QSet<QString> qmlFilesForProFile(const QString &proFile) const;
bool hasUnnamedQuickTests() const;
#ifdef WITH_TESTS
int autoTestsCount() const;
int namedQuickTestsCount() const;
int unnamedQuickTestsCount() const;
#endif
signals:
void testTreeModelChanged();
......
TEMPLATE = subdirs
SUBDIRS += src \
tests
#include <QApplication>
int main(int argc, char *argv[])