Perform parsing asynchronously...

...to avoid blocking the ui thread. Parsing will now performed
in a separate thread, except for small changes where this would
create too much overhead.

Change-Id: I1db441594f1684f969bb86c9423c0fb0bcb1a53a
Reviewed-by: Andre Poenitz <andre.poenitz@theqtcompany.com>
This commit is contained in:
Christian Stenger
2015-02-13 15:44:18 +01:00
parent 223b43e9e0
commit 4caba7a3dd
9 changed files with 166 additions and 71 deletions

View File

@@ -29,6 +29,7 @@ const char MENU_ID[] = "AutoTest.Menu";
const char AUTOTEST_ID[] = "AutoTest.ATP"; const char AUTOTEST_ID[] = "AutoTest.ATP";
const char AUTOTEST_CONTEXT[] = "Auto Tests"; const char AUTOTEST_CONTEXT[] = "Auto Tests";
const char TASK_INDEX[] = "AutoTest.Task.Index"; const char TASK_INDEX[] = "AutoTest.Task.Index";
const char TASK_PARSE[] = "AutoTest.Task.Parse";
const char UNNAMED_QUICKTESTS[] = QT_TR_NOOP("<unnamed>"); const char UNNAMED_QUICKTESTS[] = QT_TR_NOOP("<unnamed>");
const char AUTOTEST_SETTINGS_CATEGORY[] = "ZY.Tests"; const char AUTOTEST_SETTINGS_CATEGORY[] = "ZY.Tests";

View File

@@ -21,6 +21,7 @@
#include "testrunner.h" #include "testrunner.h"
#include "testsettings.h" #include "testsettings.h"
#include "testsettingspage.h" #include "testsettingspage.h"
#include "testtreeitem.h"
#include "testtreeview.h" #include "testtreeview.h"
#include "testtreemodel.h" #include "testtreemodel.h"
#include "testresultspane.h" #include "testresultspane.h"
@@ -57,7 +58,10 @@ AutotestPlugin::AutotestPlugin()
{ {
// needed to be used in QueuedConnection connects // needed to be used in QueuedConnection connects
qRegisterMetaType<TestResult>(); qRegisterMetaType<TestResult>();
// Create your members qRegisterMetaType<TestTreeItem>();
qRegisterMetaType<TestCodeLocationAndType>();
qRegisterMetaType<TestTreeModel::Type>();
m_instance = this; m_instance = this;
} }

View File

@@ -21,6 +21,7 @@
#include "testinfo.h" #include "testinfo.h"
#include "testvisitor.h" #include "testvisitor.h"
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/progressmanager/progressmanager.h>
#include <cplusplus/LookupContext.h> #include <cplusplus/LookupContext.h>
@@ -39,9 +40,13 @@
#include <qmljs/qmljsdialect.h> #include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h> #include <qmljstools/qmljsmodelmanager.h>
#include <utils/multitask.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/textfileformat.h> #include <utils/textfileformat.h>
#include <QFuture>
#include <QFutureInterface>
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
@@ -66,7 +71,7 @@ TestCodeParser::TestCodeParser(TestTreeModel *parent)
TestCodeParser::~TestCodeParser() TestCodeParser::~TestCodeParser()
{ {
clearMaps(); clearCache();
} }
void TestCodeParser::setState(State state) void TestCodeParser::setState(State state)
@@ -112,7 +117,7 @@ void TestCodeParser::updateTestTree()
m_pendingUpdate = false; m_pendingUpdate = false;
clearMaps(); clearCache();
emit cacheCleared(); emit cacheCleared();
scanForTests(); scanForTests();
} }
@@ -353,6 +358,26 @@ static TestTreeItem constructTestTreeItem(const QString &fileName,
/****** end of helpers ******/ /****** end of helpers ******/
void performParse(QFutureInterface<void> &futureInterface, QStringList list,
TestCodeParser *testCodeParser)
{
int progressValue = 0;
futureInterface.setProgressRange(0, list.size());
futureInterface.setProgressValue(progressValue);
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (const QString &file, list) {
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
futureInterface.setProgressValue(++progressValue);
testCodeParser->checkDocumentForTestCode(doc);
}
}
futureInterface.setProgressValue(list.size());
}
/****** threaded parsing stuff *******/
void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document) void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document)
{ {
const QString fileName = document->fileName(); const QString fileName = document->fileName();
@@ -445,6 +470,14 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document)
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document) void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document)
{ {
if (!m_parserEnabled) {
if (!m_pendingUpdate) {
m_partialUpdatePostPoned = true;
m_postPonedFiles.insert(document->fileName());
}
return;
}
ProjectExplorer::Project *project = currentProject(); ProjectExplorer::Project *project = currentProject();
if (!project) if (!project)
return; return;
@@ -463,6 +496,14 @@ void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &docume
void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document) void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
{ {
if (!m_parserEnabled) {
if (!m_pendingUpdate) {
m_partialUpdatePostPoned = true;
m_postPonedFiles.insert(document->fileName());
}
return;
}
ProjectExplorer::Project *project = currentProject(); ProjectExplorer::Project *project = currentProject();
if (!project) if (!project)
return; return;
@@ -482,7 +523,7 @@ void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
if (!m_quickDocMap[fileName].referencingFile().isEmpty()) if (!m_quickDocMap[fileName].referencingFile().isEmpty())
scanForTests(QStringList(m_quickDocMap[fileName].referencingFile())); scanForTests(QStringList(m_quickDocMap[fileName].referencingFile()));
} }
if (!m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS))) if (m_unnamedQuickDocList.size() == 0)
return; return;
// special case of having unnamed TestCases // special case of having unnamed TestCases
@@ -548,8 +589,10 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
if (postponed(fileList)) if (postponed(fileList))
return; return;
bool isFullParse = fileList.isEmpty();
bool isSmallChange = !isFullParse && fileList.size() < 6;
QStringList list; QStringList list;
if (fileList.isEmpty()) { if (isFullParse) {
list = currentProject()->files(ProjectExplorer::Project::AllFiles); list = currentProject()->files(ProjectExplorer::Project::AllFiles);
if (list.isEmpty()) if (list.isEmpty())
return; return;
@@ -559,34 +602,33 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
m_parserState = PartialParse; m_parserState = PartialParse;
} }
if (isSmallChange) { // no need to do this async or should we do this always async?
CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance(); CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
CPlusPlus::Snapshot snapshot = cppMM->snapshot(); CPlusPlus::Snapshot snapshot = cppMM->snapshot();
foreach (const QString &file, list) { foreach (const QString &file, list) {
if (snapshot.contains(file)) { if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value(); CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
checkDocumentForTestCode(doc); checkDocumentForTestCode(doc);
} }
} }
switch (m_parserState) { emit onFinished();
case PartialParse: return;
m_parserState = Idle;
emit partialParsingFinished();
break;
case FullParse:
m_parserState = Idle;
emit parsingFinished();
break;
default:
qWarning("I should not be here...");
break;
}
} }
void TestCodeParser::clearMaps() QFuture<void> future = QtConcurrent::run(&performParse, list, this);
Core::FutureProgress *progress
= Core::ProgressManager::addTask(future, isFullParse ? tr("Scanning for Tests")
: tr("Updating Tests"),
Autotest::Constants::TASK_PARSE);
connect(progress, &Core::FutureProgress::finished,
this, &TestCodeParser::onFinished);
}
void TestCodeParser::clearCache()
{ {
m_cppDocMap.clear(); m_cppDocMap.clear();
m_quickDocMap.clear(); m_quickDocMap.clear();
m_unnamedQuickDocList.clear();
} }
void TestCodeParser::removeTestsIfNecessary(const QString &fileName) void TestCodeParser::removeTestsIfNecessary(const QString &fileName)
@@ -611,24 +653,14 @@ void TestCodeParser::removeTestsIfNecessary(const QString &fileName)
emit testItemsRemoved(file, TestTreeModel::QuickTest); emit testItemsRemoved(file, TestTreeModel::QuickTest);
} }
// unnamed Quick Tests must be handled separately // unnamed Quick Tests must be handled separately
removeUnnamedQuickTestsByName(fileName);
QSet<QString> filePaths; QSet<QString> filePaths;
QList<QString> functionNames; QList<QString> functionNames;
if (m_model->hasUnnamedQuickTests()) { if (m_model->hasUnnamedQuickTests()) {
m_model->qmlFilesAndFunctionNamesForMainFile(fileName, &filePaths, &functionNames); m_model->qmlFilesAndFunctionNamesForMainFile(fileName, &filePaths, &functionNames);
foreach (const QString &file, filePaths) foreach (const QString &file, filePaths)
emit testItemsRemoved(file, TestTreeModel::QuickTest); emit testItemsRemoved(file, TestTreeModel::QuickTest);
// update info map
TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, functionNames)
functions.removeOne(func);
unnamedInfo.setTestFunctions(functions);
if (functions.size() == 0)
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
else
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), unnamedInfo);
} else {
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
} }
} }
} }
@@ -655,17 +687,10 @@ void TestCodeParser::removeTestsIfNecessaryByProFile(const QString &proFile)
} }
// handle unnamed Quick Tests // handle unnamed Quick Tests
const QSet<QString> &filePaths = m_model->qmlFilesForProFile(proFile); const QSet<QString> &filePaths = m_model->qmlFilesForProFile(proFile);
foreach (const QString &fileName, filePaths) foreach (const QString &fileName, filePaths) {
removeUnnamedQuickTestsByName(fileName);
emit unnamedQuickTestsRemoved(fileName); emit unnamedQuickTestsRemoved(fileName);
// update info map }
TestInfo unnamedInfo = m_quickDocMap.value(tr(Constants::UNNAMED_QUICKTESTS),
TestInfo(QString(), QStringList(), 666));
unnamedInfo.setTestFunctions(m_model->getUnnamedQuickTestFunctions());
if (unnamedInfo.testFunctions().isEmpty())
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
else
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), unnamedInfo);
} }
void TestCodeParser::onTaskStarted(Core::Id type) void TestCodeParser::onTaskStarted(Core::Id type)
@@ -683,6 +708,31 @@ void TestCodeParser::onAllTasksFinished(Core::Id type)
m_parserEnabled = true; m_parserEnabled = true;
if (m_pendingUpdate) if (m_pendingUpdate)
updateTestTree(); 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::onFinished()
{
switch (m_parserState) {
case PartialParse:
m_parserState = Idle;
emit partialParsingFinished();
break;
case FullParse:
m_parserState = Idle;
emit parsingFinished();
break;
default:
qWarning("I should not be here...");
break;
}
} }
void TestCodeParser::onPartialParsingFinished() void TestCodeParser::onPartialParsingFinished()
@@ -710,16 +760,13 @@ void TestCodeParser::updateUnnamedQuickTests(const QString &fileName, const QStr
m_quickDocMap.remove(fileName); m_quickDocMap.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::QuickTest); emit testItemsRemoved(fileName, TestTreeModel::QuickTest);
emit unnamedQuickTestsUpdated(fileName, mainFile, functions); removeUnnamedQuickTestsByName(fileName);
foreach (const QString &functionName, functions.keys()) {
if (m_model->hasUnnamedQuickTests()) { UnnamedQuickTestInfo info(functionName, fileName);
TestInfo info = m_quickDocMap.value(tr(Constants::UNNAMED_QUICKTESTS), m_unnamedQuickDocList.append(info);
TestInfo(QString(), QStringList(), 666));
info.setTestFunctions(m_model->getUnnamedQuickTestFunctions());
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), info);
} else {
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
} }
emit unnamedQuickTestsUpdated(fileName, mainFile, functions);
} }
void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document, void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document,
@@ -779,6 +826,7 @@ void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
m_quickDocMap.insert(fileName, testInfo); m_quickDocMap.insert(fileName, testInfo);
} else { } else {
// if it was formerly unnamed remove the respective items // if it was formerly unnamed remove the respective items
removeUnnamedQuickTestsByName(fileName);
emit unnamedQuickTestsRemoved(fileName); emit unnamedQuickTestsRemoved(fileName);
emit testItemCreated(testItem, TestTreeModel::QuickTest); emit testItemCreated(testItem, TestTreeModel::QuickTest);
@@ -789,6 +837,14 @@ void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
} }
} }
void TestCodeParser::removeUnnamedQuickTestsByName(const QString &fileName)
{
for (int i = m_unnamedQuickDocList.size() - 1; i >= 0; --i) {
if (m_unnamedQuickDocList.at(i).fileName() == fileName)
m_unnamedQuickDocList.removeAt(i);
}
}
void TestCodeParser::onProFileEvaluated() void TestCodeParser::onProFileEvaluated()
{ {
ProjectExplorer::Project *project = currentProject(); ProjectExplorer::Project *project = currentProject();
@@ -822,16 +878,12 @@ int TestCodeParser::autoTestsCount() const
int TestCodeParser::namedQuickTestsCount() const int TestCodeParser::namedQuickTestsCount() const
{ {
if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)))
return m_quickDocMap.size() - 1;
return m_quickDocMap.size(); return m_quickDocMap.size();
} }
int TestCodeParser::unnamedQuickTestsCount() const int TestCodeParser::unnamedQuickTestsCount() const
{ {
if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS))) return m_unnamedQuickDocList.size();
return m_quickDocMap.value(tr(Constants::UNNAMED_QUICKTESTS)).testFunctions().size();
return 0;
} }
#endif #endif

View File

@@ -38,6 +38,7 @@ namespace Internal {
struct TestCodeLocationAndType; struct TestCodeLocationAndType;
class TestInfo; class TestInfo;
class UnnamedQuickTestInfo;
class TestCodeParser : public QObject class TestCodeParser : public QObject
{ {
@@ -86,12 +87,13 @@ public slots:
private: private:
bool postponed(const QStringList &fileList); bool postponed(const QStringList &fileList);
void scanForTests(const QStringList &fileList = QStringList()); void scanForTests(const QStringList &fileList = QStringList());
void clearMaps(); void clearCache();
void removeTestsIfNecessary(const QString &fileName); void removeTestsIfNecessary(const QString &fileName);
void removeTestsIfNecessaryByProFile(const QString &proFile); void removeTestsIfNecessaryByProFile(const QString &proFile);
void onTaskStarted(Core::Id type); void onTaskStarted(Core::Id type);
void onAllTasksFinished(Core::Id type); void onAllTasksFinished(Core::Id type);
void onFinished();
void onPartialParsingFinished(); void onPartialParsingFinished();
void updateUnnamedQuickTests(const QString &fileName, const QString &mainFile, void updateUnnamedQuickTests(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions); const QMap<QString, TestCodeLocationAndType> &functions);
@@ -99,10 +101,12 @@ private:
const QString &declaringFile, TestTreeItem &testItem); const QString &declaringFile, TestTreeItem &testItem);
void updateModelAndQuickDocMap(QmlJS::Document::Ptr document, void updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
const QString &referencingFile, TestTreeItem &testItem); const QString &referencingFile, TestTreeItem &testItem);
void removeUnnamedQuickTestsByName(const QString &fileName);
TestTreeModel *m_model; TestTreeModel *m_model;
QMap<QString, TestInfo> m_cppDocMap; QMap<QString, TestInfo> m_cppDocMap;
QMap<QString, TestInfo> m_quickDocMap; QMap<QString, TestInfo> m_quickDocMap;
QList<UnnamedQuickTestInfo> m_unnamedQuickDocList;
bool m_parserEnabled; bool m_parserEnabled;
bool m_pendingUpdate; bool m_pendingUpdate;
bool m_fullUpdatePostPoned; bool m_fullUpdatePostPoned;

View File

@@ -35,5 +35,11 @@ TestInfo::~TestInfo()
m_functions.clear(); m_functions.clear();
} }
UnnamedQuickTestInfo::UnnamedQuickTestInfo(const QString &function, const QString &fileName)
: m_function(function),
m_fileName(fileName)
{
}
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -54,6 +54,22 @@ private:
QString m_proFile; QString m_proFile;
}; };
class UnnamedQuickTestInfo {
public:
explicit UnnamedQuickTestInfo(const QString &function = QString(),
const QString& fileName = QString());
~UnnamedQuickTestInfo() {}
const QString function() const { return m_function; }
void setFunction(const QString &function) { m_function = function; }
const QString fileName() const { return m_fileName; }
void setFileName(const QString &fileName) { m_fileName = fileName; }
private:
QString m_function;
QString m_fileName;
};
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -21,6 +21,7 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <QMetaType>
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
@@ -37,7 +38,8 @@ public:
TEST_SPECIALFUNCTION TEST_SPECIALFUNCTION
}; };
TestTreeItem(const QString &name, const QString &filePath, Type type, TestTreeItem *parent = 0); TestTreeItem(const QString &name = QString(), const QString &filePath = QString(),
Type type = ROOT, TestTreeItem *parent = 0);
virtual ~TestTreeItem(); virtual ~TestTreeItem();
TestTreeItem(const TestTreeItem& other); TestTreeItem(const TestTreeItem& other);
@@ -90,4 +92,7 @@ struct TestCodeLocationAndType {
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest
Q_DECLARE_METATYPE(Autotest::Internal::TestTreeItem)
Q_DECLARE_METATYPE(Autotest::Internal::TestCodeLocationAndType)
#endif // TESTTREEITEM_H #endif // TESTTREEITEM_H

View File

@@ -55,15 +55,20 @@ TestTreeModel::TestTreeModel(QObject *parent) :
m_rootItem->appendChild(m_autoTestRootItem); m_rootItem->appendChild(m_autoTestRootItem);
m_rootItem->appendChild(m_quickTestRootItem); m_rootItem->appendChild(m_quickTestRootItem);
connect(m_parser, &TestCodeParser::cacheCleared, this, &TestTreeModel::removeAllTestItems); connect(m_parser, &TestCodeParser::cacheCleared, this,
connect(m_parser, &TestCodeParser::testItemCreated, this, &TestTreeModel::addTestTreeItem); &TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemsCreated, this, &TestTreeModel::addTestTreeItems); connect(m_parser, &TestCodeParser::testItemCreated,
connect(m_parser, &TestCodeParser::testItemModified, this, &TestTreeModel::modifyTestTreeItem); this, &TestTreeModel::addTestTreeItem, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemsRemoved, this, &TestTreeModel::removeTestTreeItems); connect(m_parser, &TestCodeParser::testItemsCreated,
this, &TestTreeModel::addTestTreeItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemModified,
this, &TestTreeModel::modifyTestTreeItem, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testItemsRemoved,
this, &TestTreeModel::removeTestTreeItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated, connect(m_parser, &TestCodeParser::unnamedQuickTestsUpdated,
this, &TestTreeModel::updateUnnamedQuickTest); this, &TestTreeModel::updateUnnamedQuickTest, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved, connect(m_parser, &TestCodeParser::unnamedQuickTestsRemoved,
this, &TestTreeModel::removeUnnamedQuickTests); this, &TestTreeModel::removeUnnamedQuickTests, Qt::QueuedConnection);
// CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance(); // CppTools::CppModelManagerInterface *cppMM = CppTools::CppModelManagerInterface::instance();
// if (cppMM) { // if (cppMM) {

View File

@@ -152,4 +152,6 @@ private:
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest
Q_DECLARE_METATYPE(Autotest::Internal::TestTreeModel::Type)
#endif // TESTTREEMODEL_H #endif // TESTTREEMODEL_H