Commit 992f9ed2 authored by Christian Stenger's avatar Christian Stenger
Browse files

AutoTest: Use mark and sweep for parsing



This also removes caching from parser and the respective test info
files are gone now as well.

Change-Id: Ibcea68e446dea532d6addd2f16863738e497bca4
Reviewed-by: default avatarDavid Schulz <david.schulz@theqtcompany.com>
parent fd4d1c80
......@@ -11,7 +11,6 @@ SOURCES += \
testtreemodel.cpp \
testtreeitem.cpp \
testvisitor.cpp \
testinfo.cpp \
testcodeparser.cpp \
autotestplugin.cpp \
testrunner.cpp \
......@@ -31,7 +30,6 @@ HEADERS += \
testtreemodel.h \
testtreeitem.h \
testvisitor.h \
testinfo.h \
testcodeparser.h \
autotestplugin.h \
autotest_global.h \
......
......@@ -40,8 +40,6 @@ QtcPlugin {
"testcodeparser.h",
"testconfiguration.cpp",
"testconfiguration.h",
"testinfo.cpp",
"testinfo.h",
"testnavigationwidget.cpp",
"testnavigationwidget.h",
"testresult.cpp",
......
......@@ -105,11 +105,6 @@ void AutoTestUnitTests::testCodeParser()
QCOMPARE(m_model->namedQuickTestsCount(), expectedNamedQuickTestsCount);
QCOMPARE(m_model->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount);
QCOMPARE(m_model->dataTagsCount(), expectedDataTagsCount);
QCOMPARE(m_model->parser()->autoTestsCount(), expectedAutoTestsCount);
QCOMPARE(m_model->parser()->namedQuickTestsCount(), expectedNamedQuickTestsCount);
QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), expectedUnnamedQuickTestsCount);
}
void AutoTestUnitTests::testCodeParser_data()
......@@ -162,12 +157,6 @@ void AutoTestUnitTests::testCodeParserSwitchStartup()
m_isQt4 ? 0 : expectedUnnamedQuickTestsCount.at(i));
QCOMPARE(m_model->dataTagsCount(),
expectedDataTagsCount.at(i));
QCOMPARE(m_model->parser()->autoTestsCount(), expectedAutoTestsCount.at(i));
QCOMPARE(m_model->parser()->namedQuickTestsCount(),
m_isQt4 ? 0 : expectedNamedQuickTestsCount.at(i));
QCOMPARE(m_model->parser()->unnamedQuickTestsCount(),
m_isQt4 ? 0 : expectedUnnamedQuickTestsCount.at(i));
}
}
......@@ -212,8 +201,6 @@ void AutoTestUnitTests::testCodeParserGTest()
QVERIFY(parserSpy.wait(20000));
QCOMPARE(m_model->gtestNamesCount(), 6);
// 11 == 3 + 2 + 2 + 2 + 1 + 1, see below
QCOMPARE(m_model->parser()->gtestNamesAndSetsCount(), 11);
QMultiMap<QString, int> expectedNamesAndSets;
expectedNamesAndSets.insert(QStringLiteral("FactorialTest"), 3);
......@@ -233,10 +220,6 @@ void AutoTestUnitTests::testCodeParserGTest()
QCOMPARE(m_model->namedQuickTestsCount(), 0);
QCOMPARE(m_model->unnamedQuickTestsCount(), 0);
QCOMPARE(m_model->dataTagsCount(), 0);
QCOMPARE(m_model->parser()->autoTestsCount(), 0);
QCOMPARE(m_model->parser()->namedQuickTestsCount(), 0);
QCOMPARE(m_model->parser()->unnamedQuickTestsCount(), 0);
}
} // namespace Internal
......
......@@ -26,7 +26,6 @@
#include "autotestconstants.h"
#include "autotest_utils.h"
#include "testcodeparser.h"
#include "testinfo.h"
#include "testvisitor.h"
#include <coreplugin/editormanager/editormanager.h>
......@@ -84,7 +83,6 @@ TestCodeParser::TestCodeParser(TestTreeModel *parent)
TestCodeParser::~TestCodeParser()
{
clearCache();
}
void TestCodeParser::setState(State state)
......@@ -145,7 +143,7 @@ void TestCodeParser::updateTestTree()
m_fullUpdatePostponed = false;
clearCache();
emit aboutToPerformFullParse();
qCDebug(LOG) << "calling scanForTests (updateTestTree)";
scanForTests();
}
......@@ -488,7 +486,7 @@ static TestTreeItem *constructGTestTreeItem(const QString &filePath, const GTest
static bool parsingHasFailed;
void performParse(QFutureInterface<void> &futureInterface, const QStringList &list,
const QMap<QString, TestCodeParser::ReferencingInfo> &referencingFiles,
const QMap<QString, QString> &referencingFiles,
TestCodeParser *testCodeParser)
{
int progressValue = 0;
......@@ -501,8 +499,9 @@ void performParse(QFutureInterface<void> &futureInterface, const QStringList &li
if (snapshot.contains(file)) {
CPlusPlus::Document::Ptr doc = snapshot.find(file).value();
futureInterface.setProgressValue(++progressValue);
const QString &referencingFile = referencingFiles.value(file);
testCodeParser->checkDocumentForTestCode(doc,
referencingFiles.value(file).referencingFile);
list.contains(referencingFile) ? QString() : referencingFile);
} else {
parsingHasFailed |= (CppTools::ProjectFile::classify(file)
!= CppTools::ProjectFile::Unclassified);
......@@ -520,10 +519,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document,
QList<CppTools::ProjectPart::Ptr> projParts = modelManager->projectPart(fileName);
if (projParts.size())
if (!projParts.at(0)->selectedForBuilding) {
removeTestsIfNecessary(fileName);
if (!projParts.at(0)->selectedForBuilding)
return;
}
if (includesQtQuickTest(document, modelManager)) {
handleQtQuickTest(document);
......@@ -540,6 +537,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document,
if (declaringDoc.isNull())
return;
const bool hasReferencingFile = declaringDoc->fileName() != document->fileName();
TestVisitor visitor(testCaseName);
visitor.accept(declaringDoc->globalNamespace());
const QMap<QString, TestCodeLocationAndType> testFunctions = visitor.privateSlots();
......@@ -547,14 +546,16 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document,
QMap<QString, TestCodeLocationList> dataTags =
checkForDataTags(declaringDoc->fileName(), testFunctions);
if (declaringDoc->fileName() != document->fileName())
if (hasReferencingFile)
dataTags.unite(checkForDataTags(document->fileName(), testFunctions));
TestTreeItem *item = constructTestTreeItem(declaringDoc->fileName(), QString(),
testCaseName, line, column, testFunctions,
dataTags);
if (hasReferencingFile)
item->setReferencingFile(fileName);
updateModelAndCppDocMap(document, declaringDoc->fileName(), item);
emit testItemCreated(item, TestTreeModel::AutoTest);
return;
}
}
......@@ -570,12 +571,8 @@ void TestCodeParser::checkDocumentForTestCode(CPlusPlus::Document::Ptr document,
// maybe file is only a referenced file
if (!referencingFile.isEmpty()) {
CPlusPlus::Snapshot snapshot = modelManager->snapshot();
if (snapshot.contains(referencingFile)) {
if (snapshot.contains(referencingFile))
checkDocumentForTestCode(snapshot.find(referencingFile).value());
} else { // no referencing file too, so this test case is no more a test case
m_referencingFiles.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::AutoTest);
}
}
}
......@@ -602,19 +599,28 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr document)
const TestCodeLocationAndType tcLocationAndType = qmlVisitor.testCaseLocation();
const QMap<QString, TestCodeLocationAndType> testFunctions = qmlVisitor.testFunctions();
TestTreeItem *testTreeItem;
if (testCaseName.isEmpty()) {
updateUnnamedQuickTests(qmlJSDoc->fileName(), cppFileName, testFunctions);
continue;
} // end of handling test cases without name property
// construct new/modified TestTreeItem
TestTreeItem *testTreeItem
= constructTestTreeItem(tcLocationAndType.m_name, cppFileName, testCaseName,
tcLocationAndType.m_line, tcLocationAndType.m_column,
testFunctions);
testTreeItem = new TestTreeItem(QString(), QString(), TestTreeItem::TestClass);
foreach (const QString &functionName, testFunctions.keys()) {
const TestCodeLocationAndType locationAndType = testFunctions.value(functionName);
TestTreeItem *testFunction = new TestTreeItem(functionName, locationAndType.m_name,
locationAndType.m_type);
testFunction->setLine(locationAndType.m_line);
testFunction->setColumn(locationAndType.m_column);
testFunction->setMainFile(cppFileName);
testFunction->setReferencingFile(cppFileName);
testTreeItem->appendChild(testFunction);
}
// update model and internal map
updateModelAndQuickDocMap(qmlJSDoc, cppFileName, testTreeItem);
} else {
testTreeItem = constructTestTreeItem(tcLocationAndType.m_name, cppFileName,
testCaseName, tcLocationAndType.m_line,
tcLocationAndType.m_column, testFunctions);
testTreeItem->setReferencingFile(cppFileName);
}
emit testItemCreated(testTreeItem, TestTreeModel::QuickTest);
}
}
......@@ -629,7 +635,17 @@ void TestCodeParser::handleGTest(const QString &filePath)
visitor.accept(ast);
QMap<GTestCaseSpec, TestCodeLocationList> result = visitor.gtestFunctions();
updateGTests(document, result);
QString proFile;
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(filePath);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
foreach (const GTestCaseSpec &testSpec, result.keys()) {
TestTreeItem *item = constructGTestTreeItem(filePath, testSpec, proFile,
result.value(testSpec));
emit testItemCreated(item, TestTreeModel::GoogleTest);
}
}
void TestCodeParser::onCppDocumentUpdated(const CPlusPlus::Document::Ptr &document)
......@@ -672,26 +688,15 @@ void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document)
return;
}
const CPlusPlus::Snapshot snapshot = CppTools::CppModelManager::instance()->snapshot();
if (m_referencingFiles.contains(fileName)) {
const ReferencingInfo &refInfo = m_referencingFiles.value(fileName);
if (refInfo.type == TestTreeModel::QuickTest
&& snapshot.contains(refInfo.referencingFile)) {
if (!refInfo.referencingFile.isEmpty()) {
qCDebug(LOG) << "calling scanForTests with cached referencing files"
<< "(onQmlDocumentUpdated)";
scanForTests(QStringList(refInfo.referencingFile));
}
QMap<QString, QString> referencingFiles = m_model->referencingFiles();
if (referencingFiles.contains(fileName)) {
const QString &referencingFile = referencingFiles.value(fileName);
if (!referencingFile.isEmpty() && snapshot.contains(referencingFile)) {
qCDebug(LOG) << "calling scanForTests with cached referencing files"
<< "(onQmlDocumentUpdated)";
scanForTests(QStringList(referencingFile));
}
}
if (m_unnamedQuickDocList.size() == 0)
return;
// special case of having unnamed TestCases
const QString &mainFile = m_model->getMainFileForUnnamedQuickTest(fileName);
if (!mainFile.isEmpty() && snapshot.contains(mainFile)) {
qCDebug(LOG) << "calling scanForTests with mainfile (onQmlDocumentUpdated)";
scanForTests(QStringList(mainFile));
}
}
void TestCodeParser::onStartupProjectChanged(ProjectExplorer::Project *)
......@@ -699,7 +704,7 @@ void TestCodeParser::onStartupProjectChanged(ProjectExplorer::Project *)
if (m_parserState == FullParse || m_parserState == PartialParse) {
Core::ProgressManager::instance()->cancelTasks(Constants::TASK_PARSE);
} else {
clearCache();
emit aboutToPerformFullParse();
emitUpdateTestTree();
}
}
......@@ -714,12 +719,6 @@ void TestCodeParser::onProjectPartsUpdated(ProjectExplorer::Project *project)
emitUpdateTestTree();
}
void TestCodeParser::removeFiles(const QStringList &files)
{
foreach (const QString &file, files)
removeTestsIfNecessary(file);
}
bool TestCodeParser::postponed(const QStringList &fileList)
{
switch (m_parserState) {
......@@ -786,13 +785,20 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
}
parsingHasFailed = false;
QMap<QString, QString> referencingFiles = m_model->referencingFiles();
if (isSmallChange) { // no need to do this async or should we do this always async?
foreach (const QString &filePath, list)
m_model->markForRemoval(filePath);
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();
checkDocumentForTestCode(doc, m_referencingFiles.value(file).referencingFile);
const QString &referencingFile = referencingFiles.value(file);
checkDocumentForTestCode(doc, list.contains(referencingFile) ? QString()
: referencingFile);
} else {
parsingHasFailed |= (CppTools::ProjectFile::classify(file)
!= CppTools::ProjectFile::Unclassified);
......@@ -802,7 +808,9 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
return;
}
QFuture<void> future = Utils::runAsync<void>(&performParse, list, m_referencingFiles, this);
m_model->markAllForRemoval();
QFuture<void> future = Utils::runAsync<void>(&performParse, list, referencingFiles, this);
Core::FutureProgress *progress
= Core::ProgressManager::addTask(future, isFullParse ? tr("Scanning for Tests")
: tr("Refreshing Tests List"),
......@@ -813,53 +821,6 @@ void TestCodeParser::scanForTests(const QStringList &fileList)
emit parsingStarted();
}
void TestCodeParser::clearCache()
{
m_referencingFiles.clear();
m_unnamedQuickDocList.clear();
m_gtestDocList.clear();
emit cacheCleared();
}
void TestCodeParser::removeTestsIfNecessary(const QString &fileName)
{
// check if this file was listed before and remove if necessary (switched config,...)
if (m_referencingFiles.contains(fileName)
&& m_referencingFiles.value(fileName).type == TestTreeModel::AutoTest) {
m_referencingFiles.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::AutoTest);
} else if (m_referencingFiles.contains(fileName)
&& m_referencingFiles.value(fileName).type == TestTreeModel::GoogleTest) {
m_referencingFiles.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::GoogleTest);
} else { // handle Qt Quick Tests
QList<QString> toBeRemoved;
foreach (const QString &file, m_referencingFiles.keys()) {
if (file == fileName
&& m_referencingFiles.value(file).type == TestTreeModel::QuickTest) {
toBeRemoved.append(file);
continue;
}
const ReferencingInfo &refInfo = m_referencingFiles.value(file);
if (refInfo.type == TestTreeModel::QuickTest && refInfo.referencingFile == fileName)
toBeRemoved.append(file);
}
foreach (const QString &file, toBeRemoved) {
m_referencingFiles.remove(file);
emit testItemsRemoved(file, TestTreeModel::QuickTest);
}
// unnamed Quick Tests must be handled separately
if (fileName.endsWith(QLatin1String(".qml"))) {
removeUnnamedQuickTestsByName(fileName);
} else {
QSet<QString> filePaths;
m_model->qmlFilesForMainFile(fileName, &filePaths);
foreach (const QString &file, filePaths)
removeUnnamedQuickTestsByName(file);
}
}
}
void TestCodeParser::onTaskStarted(Core::Id type)
{
if (type == CppTools::Constants::TASK_INDEX)
......@@ -936,175 +897,5 @@ void TestCodeParser::onPartialParsingFinished()
}
}
void TestCodeParser::updateUnnamedQuickTests(const QString &fileName, const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions)
{
// if this test case was named before remove it
m_referencingFiles.remove(fileName);
emit testItemsRemoved(fileName, TestTreeModel::QuickTest);
removeUnnamedQuickTestsByName(fileName);
foreach (const QString &functionName, functions.keys()) {
UnnamedQuickTestInfo info(functionName, fileName);
m_unnamedQuickDocList.append(info);
}
emit unnamedQuickTestsUpdated(mainFile, functions);
}
void TestCodeParser::updateModelAndCppDocMap(CPlusPlus::Document::Ptr document,
const QString &declaringFile, TestTreeItem *testItem)
{
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
const QString fileName = document->fileName();
const QString testCaseName = testItem->name();
QString proFile;
const QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(fileName);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
if (m_referencingFiles.contains(fileName)
&& m_referencingFiles.value(fileName).type == TestTreeModel::AutoTest) {
QStringList files = { fileName };
if (fileName != declaringFile)
files << declaringFile;
foreach (const QString &file, files) {
const bool setReferencingFile = (files.size() == 2 && file == declaringFile);
ReferencingInfo testInfo;
if (setReferencingFile) {
testInfo.referencingFile = fileName;
testItem->setReferencingFile(fileName);
}
testInfo.type = TestTreeModel::AutoTest;
m_referencingFiles.insert(file, testInfo);
}
emit testItemModified(testItem, TestTreeModel::AutoTest, files);
} else {
if (declaringFile != fileName)
testItem->setReferencingFile(fileName);
emit testItemCreated(testItem, TestTreeModel::AutoTest);
ReferencingInfo testInfo;
testInfo.type = TestTreeModel::AutoTest;
m_referencingFiles.insert(fileName, testInfo);
if (declaringFile != fileName) {
testInfo.referencingFile = fileName;
m_referencingFiles.insert(declaringFile, testInfo);
}
}
}
void TestCodeParser::updateModelAndQuickDocMap(QmlJS::Document::Ptr document,
const QString &referencingFile,
TestTreeItem *testItem)
{
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
const QString fileName = document->fileName();
QString proFile;
QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(referencingFile);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
if (m_referencingFiles.contains(fileName)
&& m_referencingFiles.value(fileName).type == TestTreeModel::QuickTest) {
ReferencingInfo testInfo;
testInfo.referencingFile = referencingFile;
testInfo.type = TestTreeModel::QuickTest;
testItem->setReferencingFile(referencingFile);
emit testItemModified(testItem, TestTreeModel::QuickTest, { fileName });
m_referencingFiles.insert(fileName, testInfo);
} else {
// if it was formerly unnamed remove the respective items
removeUnnamedQuickTestsByName(fileName);
const QString &filePath = testItem->filePath();
ReferencingInfo testInfo;
testInfo.referencingFile = referencingFile;
testInfo.type = TestTreeModel::QuickTest;
testItem->setReferencingFile(referencingFile);
emit testItemCreated(testItem, TestTreeModel::QuickTest);
m_referencingFiles.insert(filePath, testInfo);
}
}
void TestCodeParser::updateGTests(const CPlusPlus::Document::Ptr &doc,
const QMap<GTestCaseSpec, TestCodeLocationList> &tests)
{
const QString &fileName = doc->fileName();
removeGTestsByName(fileName);
QString proFile;
const CppTools::CppModelManager *cppMM = CppTools::CppModelManager::instance();
QList<CppTools::ProjectPart::Ptr> ppList = cppMM->projectPart(fileName);
if (ppList.size())
proFile = ppList.at(0)->projectFile;
foreach (const GTestCaseSpec &testSpec, tests.keys()) {
TestTreeItem *item = constructGTestTreeItem(fileName, testSpec, proFile, tests.value(testSpec));
ReferencingInfo testInfo;
testInfo.type = TestTreeModel::GoogleTest;
foreach (const TestCodeLocationAndType &testSet, tests.value(testSpec)) {
GTestInfo gtestInfo(testSpec.testCaseName, testSet.m_name, fileName);
if (testSet.m_state & TestTreeItem::Disabled)
gtestInfo.setEnabled(false);
m_gtestDocList.append(gtestInfo);
}
emit testItemCreated(item, TestTreeModel::GoogleTest);
m_referencingFiles.insert(fileName, testInfo);
}
}
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);
}
emit unnamedQuickTestsRemoved(fileName);
}
void TestCodeParser::removeGTestsByName(const QString &fileName)
{
for (int i = m_gtestDocList.size() - 1; i >= 0; --i)
if (m_gtestDocList.at(i).fileName() == fileName)
m_gtestDocList.removeAt(i);
emit gTestsRemoved(fileName);
}
#ifdef WITH_TESTS
int TestCodeParser::autoTestsCount() const
{
int count = 0;
foreach (const QString &file, m_referencingFiles.keys()) {
const ReferencingInfo &refInfo = m_referencingFiles.value(file);
if (refInfo.type == TestTreeModel::AutoTest && refInfo.referencingFile.isEmpty())
++count;
}
return count;
}
int TestCodeParser::namedQuickTestsCount() const
{
int count = 0;
foreach (const QString &file, m_referencingFiles.keys()) {
const ReferencingInfo &refInfo = m_referencingFiles.value(file);
if (refInfo.type == TestTreeModel::QuickTest)
++count;
}
return count;
}
int TestCodeParser::unnamedQuickTestsCount() const
{
return m_unnamedQuickDocList.size();
}
int TestCodeParser::gtestNamesAndSetsCount() const
{
return m_gtestDocList.size();
}
#endif
} // namespace Internal
} // namespace Autotest
......@@ -44,8 +44,6 @@ namespace Autotest {
namespace Internal {
struct TestCodeLocationAndType;
class UnnamedQuickTestInfo;
class GTestInfo;
struct GTestCaseSpec;
class TestCodeParser : public QObject
......@@ -65,29 +63,9 @@ public:
State state() const { return m_parserState; }
void setDirty() { m_dirty = true; }
#ifdef WITH_TESTS
int autoTestsCount() const;
int namedQuickTestsCount() const;
int unnamedQuickTestsCount() const;
int gtestNamesAndSetsCount() const;
#endif
// FIXME remove me again
struct ReferencingInfo
{
QString referencingFile;
TestTreeModel::Type type;
};
signals:
void cacheCleared();
void aboutToPerformFullParse();
void testItemCreated(TestTreeItem *item, TestTreeModel::Type type);
void testItemModified(TestTreeItem *tItem, TestTreeModel::Type type, const QStringList &file);
void testItemsRemoved(const QString &filePath, TestTreeModel::Type type);
void unnamedQuickTestsUpdated(const QString &mainFile,
const QMap<QString, TestCodeLocationAndType> &functions);
void unnamedQuickTestsRemoved(const QString &filePath);
void gTestsRemoved(const QString &filePath);
void parsingStarted();
void parsingFinished();
void parsingFailed();
......@@ -105,34 +83,18 @@ public slots:
void onQmlDocumentUpdated(const QmlJS::Document::Ptr &document);
void onStartupProjectChanged(ProjectExplorer::Project *);
void onProjectPartsUpdated(ProjectExplorer::Project *project);
void removeFiles(const QStringList &files);
private:
bool postponed(const QStringList &fileList);
void scanForTests(const QStringList &fileList = QStringList());
void clearCache();
void removeTestsIfNecessary(const QString &fileName);
void onTaskStarted(Core::Id type);
void onAllTasksFinished(Core::Id type);
void onFinished();
void onPartialParsingFinished();
void updateUnnamedQuickTests(