AutoTest: Optimize TestCodeParser::scanForTests

In case of loading a Creator project, after the Scanning
For Tests finished, the scanForTests() called by
TestCodeParser::onFinished() freezed the main thread for
about 1 second. In this case requestRemoval() signal was
emitted nearly 1000 times.

Optimize the internals:

1. Don't emit requestRemoval() for every single file but
   emit it just once passing a QSet<FilePath> instead.
2. Adapt some other callees and callers to work on
   QSet<FilePath> instead on a single FilePath.

This change constraints the freeze to about 2 ms.

Change-Id: If23b85b495c125d82eb3c8b5a6912349df122745
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Jarek Kobus
2023-04-23 15:29:11 +02:00
parent cc14eaf570
commit e849c19a33
8 changed files with 33 additions and 46 deletions

View File

@@ -389,17 +389,19 @@ QSet<QString> internalTargets(const FilePath &proFile)
return result; return result;
} }
void QuickTestTreeItem::markForRemovalRecursively(const FilePath &filePath) void QuickTestTreeItem::markForRemovalRecursively(const QSet<FilePath> &filePaths)
{ {
TestTreeItem::markForRemovalRecursively(filePath); TestTreeItem::markForRemovalRecursively(filePaths);
auto parser = static_cast<QuickTestParser *>(framework()->testParser()); auto parser = static_cast<QuickTestParser *>(framework()->testParser());
const FilePath proFile = parser->projectFileForMainCppFile(filePath); for (const FilePath &filePath : filePaths) {
if (!proFile.isEmpty()) { const FilePath proFile = parser->projectFileForMainCppFile(filePath);
TestTreeItem *root = framework()->rootNode(); if (!proFile.isEmpty()) {
root->forAllChildItems([proFile](TestTreeItem *it) { TestTreeItem *root = framework()->rootNode();
if (it->proFile() == proFile) root->forAllChildItems([proFile](TestTreeItem *it) {
it->markForRemoval(true); if (it->proFile() == proFile)
}); it->markForRemoval(true);
});
}
} }
} }

View File

@@ -35,7 +35,7 @@ public:
bool removeOnSweepIfEmpty() const override; bool removeOnSweepIfEmpty() const override;
TestTreeItem *createParentGroupNode() const override; TestTreeItem *createParentGroupNode() const override;
bool isGroupable() const override; bool isGroupable() const override;
void markForRemovalRecursively(const Utils::FilePath &filePath) override; void markForRemovalRecursively(const QSet<Utils::FilePath> &filePaths) override;
private: private:
TestTreeItem *findChildByFileNameAndType(const Utils::FilePath &filePath, const QString &name, TestTreeItem *findChildByFileNameAndType(const Utils::FilePath &filePath, const QString &name,
Type tType); Type tType);

View File

@@ -307,24 +307,20 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QList<ITestPa
TestTreeModel::instance()->updateCheckStateCache(); TestTreeModel::instance()->updateCheckStateCache();
if (isFullParse) { if (isFullParse) {
// remove qml files as they will be found automatically by the referencing cpp file // remove qml files as they will be found automatically by the referencing cpp file
list = Utils::filtered(list, [](const FilePath &fn) { list = Utils::filtered(list, [](const FilePath &fn) { return !fn.endsWith(".qml"); });
return !fn.endsWith(".qml");
});
if (!parsers.isEmpty()) { if (!parsers.isEmpty()) {
for (ITestParser *parser : parsers) { for (ITestParser *parser : parsers)
parser->framework()->rootNode()->markForRemovalRecursively(true); parser->framework()->rootNode()->markForRemovalRecursively(true);
}
} else { } else {
emit requestRemoveAllFrameworkItems(); emit requestRemoveAllFrameworkItems();
} }
} else if (!parsers.isEmpty()) { } else if (!parsers.isEmpty()) {
const auto set = Utils::toSet(list);
for (ITestParser *parser: parsers) { for (ITestParser *parser: parsers) {
for (const FilePath &filePath : std::as_const(list)) parser->framework()->rootNode()->markForRemovalRecursively(set);
parser->framework()->rootNode()->markForRemovalRecursively(filePath);
} }
} else { } else {
for (const FilePath &filePath : std::as_const(list)) emit requestRemoval(Utils::toSet(list));
emit requestRemoval(filePath);
} }
QTC_ASSERT(!(isFullParse && list.isEmpty()), onFinished(true); return); QTC_ASSERT(!(isFullParse && list.isEmpty()), onFinished(true); return);

View File

@@ -53,7 +53,7 @@ signals:
void parsingStarted(); void parsingStarted();
void parsingFinished(); void parsingFinished();
void parsingFailed(); void parsingFailed();
void requestRemoval(const Utils::FilePath &filePath); void requestRemoval(const QSet<Utils::FilePath> &filePaths);
void requestRemoveAllFrameworkItems(); void requestRemoveAllFrameworkItems();
public: public:

View File

@@ -224,11 +224,11 @@ void TestTreeItem::markForRemovalRecursively(bool mark)
childItem(row)->markForRemovalRecursively(mark); childItem(row)->markForRemovalRecursively(mark);
} }
void TestTreeItem::markForRemovalRecursively(const FilePath &filepath) void TestTreeItem::markForRemovalRecursively(const QSet<FilePath> &filePaths)
{ {
bool mark = filePath() == filepath; bool mark = filePaths.contains(filePath());
forFirstLevelChildItems([&mark, &filepath](TestTreeItem *child) { forFirstLevelChildItems([&mark, &filePaths](TestTreeItem *child) {
child->markForRemovalRecursively(filepath); child->markForRemovalRecursively(filePaths);
mark &= child->markedForRemoval(); mark &= child->markedForRemoval();
}); });
markForRemoval(mark); markForRemoval(mark);

View File

@@ -115,7 +115,7 @@ public:
void setProFile(const Utils::FilePath &proFile) { m_proFile = proFile; } void setProFile(const Utils::FilePath &proFile) { m_proFile = proFile; }
void markForRemoval(bool mark); void markForRemoval(bool mark);
void markForRemovalRecursively(bool mark); void markForRemovalRecursively(bool mark);
virtual void markForRemovalRecursively(const Utils::FilePath &filepath); virtual void markForRemovalRecursively(const QSet<Utils::FilePath> &filePaths);
virtual bool removeOnSweepIfEmpty() const { return type() == GroupNode; } virtual bool removeOnSweepIfEmpty() const { return type() == GroupNode; }
bool markedForRemoval() const { return m_status == MarkedForRemoval; } bool markedForRemoval() const { return m_status == MarkedForRemoval; }
bool newlyAdded() const { return m_status == NewlyAdded; } bool newlyAdded() const { return m_status == NewlyAdded; }

View File

@@ -100,8 +100,8 @@ void TestTreeModel::setupParsingConnections()
m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection); m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection);
connect(cppMM, &CppEditor::CppModelManager::aboutToRemoveFiles, connect(cppMM, &CppEditor::CppModelManager::aboutToRemoveFiles,
this, [this](const QStringList &files) { this, [this](const QStringList &files) {
const FilePaths filesToRemove = FileUtils::toFilePathList(files); markForRemoval(transform<QSet>(files, &FilePath::fromString));
removeFiles(filesToRemove); sweep();
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
connect(cppMM, &CppEditor::CppModelManager::projectPartsUpdated, connect(cppMM, &CppEditor::CppModelManager::projectPartsUpdated,
m_parser, &TestCodeParser::onProjectPartsUpdated); m_parser, &TestCodeParser::onProjectPartsUpdated);
@@ -109,11 +109,11 @@ void TestTreeModel::setupParsingConnections()
QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance(); QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance();
connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated, connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated,
m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection); m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection);
connect(qmlJsMM, connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles,
&QmlJS::ModelManagerInterface::aboutToRemoveFiles, this, [this](const FilePaths &filePaths) {
this, markForRemoval(Utils::toSet(filePaths));
&TestTreeModel::removeFiles, sweep();
Qt::QueuedConnection); }, Qt::QueuedConnection);
connectionsInitialized = true; connectionsInitialized = true;
} }
@@ -463,13 +463,6 @@ void TestTreeModel::clearFailedMarks()
m_failedStateCache.clear(); m_failedStateCache.clear();
} }
void TestTreeModel::removeFiles(const FilePaths &files)
{
for (const FilePath &file : files)
markForRemoval(file);
sweep();
}
void TestTreeModel::markAllFrameworkItemsForRemoval() void TestTreeModel::markAllFrameworkItemsForRemoval()
{ {
for (TestTreeItem *frameworkRoot : frameworkRootNodes()) { for (TestTreeItem *frameworkRoot : frameworkRootNodes()) {
@@ -479,15 +472,12 @@ void TestTreeModel::markAllFrameworkItemsForRemoval()
} }
} }
void TestTreeModel::markForRemoval(const FilePath &filePath) void TestTreeModel::markForRemoval(const QSet<Utils::FilePath> &filePaths)
{ {
if (filePath.isEmpty())
return;
for (TestTreeItem *frameworkRoot : frameworkRootNodes()) { for (TestTreeItem *frameworkRoot : frameworkRootNodes()) {
for (int childRow = frameworkRoot->childCount() - 1; childRow >= 0; --childRow) { for (int childRow = frameworkRoot->childCount() - 1; childRow >= 0; --childRow) {
TestTreeItem *child = frameworkRoot->childItem(childRow); TestTreeItem *child = frameworkRoot->childItem(childRow);
child->markForRemovalRecursively(filePath); child->markForRemovalRecursively(filePaths);
} }
} }
} }

View File

@@ -66,7 +66,7 @@ public:
#endif #endif
void markAllFrameworkItemsForRemoval(); void markAllFrameworkItemsForRemoval();
void markForRemoval(const Utils::FilePath &filePath); void markForRemoval(const QSet<Utils::FilePath> &filePaths);
void sweep(); void sweep();
signals: signals:
@@ -83,7 +83,6 @@ private:
void handleParseResult(const TestParseResult *result, TestTreeItem *rootNode); void handleParseResult(const TestParseResult *result, TestTreeItem *rootNode);
void removeAllTestItems(); void removeAllTestItems();
void removeAllTestToolItems(); void removeAllTestToolItems();
void removeFiles(const Utils::FilePaths &files);
bool sweepChildren(TestTreeItem *item); bool sweepChildren(TestTreeItem *item);
void insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled); void insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled);
void revalidateCheckState(ITestTreeItem *item); void revalidateCheckState(ITestTreeItem *item);