2023-05-12 21:38:33 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
#include <app/app_version.h>
|
|
|
|
|
|
|
|
|
|
#include <utils/async.h>
|
|
|
|
|
#include <utils/filesearch.h>
|
|
|
|
|
#include <utils/launcherinterface.h>
|
|
|
|
|
#include <utils/scopedtimer.h>
|
|
|
|
|
#include <utils/temporarydirectory.h>
|
|
|
|
|
|
|
|
|
|
#include <QDirIterator>
|
|
|
|
|
#include <QObject>
|
|
|
|
|
#include <QTemporaryDir>
|
|
|
|
|
#include <QtTest>
|
|
|
|
|
|
|
|
|
|
#include <unordered_set>
|
|
|
|
|
|
|
|
|
|
using namespace Tasking;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
static const int s_topLevelSubDirsCount = 128;
|
|
|
|
|
static const int s_subDirsCount = 4;
|
|
|
|
|
static const int s_subFilesCount = 4;
|
|
|
|
|
static const int s_treeDepth = 5;
|
2023-05-12 21:38:33 +02:00
|
|
|
|
|
|
|
|
static const QDir::Filters s_filters = QDir::Dirs | QDir::Files | QDir::Hidden
|
|
|
|
|
| QDir::NoDotAndDotDot;
|
|
|
|
|
|
|
|
|
|
static const char s_dirPrefix[] = "dir_";
|
|
|
|
|
static const char s_filePrefix[] = "file_";
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
static QString dirName(int suffix)
|
|
|
|
|
{
|
|
|
|
|
return QString("%1%2").arg(s_dirPrefix).arg(suffix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString fileName(int suffix)
|
|
|
|
|
{
|
|
|
|
|
return QString("%1%2.txt").arg(s_filePrefix).arg(suffix);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 21:38:33 +02:00
|
|
|
static int expectedDirsCountHelper(int depth)
|
|
|
|
|
{
|
|
|
|
|
if (depth == 1)
|
|
|
|
|
return s_subDirsCount + 1; // +1 -> dir itself
|
|
|
|
|
return expectedDirsCountHelper(depth - 1) * s_subDirsCount + 1; // +1 -> dir itself
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
static int expectedDirsCount()
|
2023-05-12 21:38:33 +02:00
|
|
|
{
|
2023-05-19 11:53:44 +02:00
|
|
|
return expectedDirsCountHelper(s_treeDepth) * s_topLevelSubDirsCount;
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int expectedFilesCountHelper(int depth)
|
|
|
|
|
{
|
|
|
|
|
if (depth == 0)
|
|
|
|
|
return s_subFilesCount;
|
|
|
|
|
return expectedFilesCountHelper(depth - 1) * s_subDirsCount + s_subFilesCount;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
static int expectedFilesCount()
|
2023-05-12 21:38:33 +02:00
|
|
|
{
|
2023-05-19 11:53:44 +02:00
|
|
|
return expectedFilesCountHelper(s_treeDepth) * s_topLevelSubDirsCount + 1; // +1 -> fileTemplate.txt
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
static int threadsCount()
|
|
|
|
|
{
|
|
|
|
|
return qMax(QThread::idealThreadCount() - 1, 1); // "- 1" -> left for main thread
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool generateOriginal(const QString &parentPath, const QString &templateFile, int depth)
|
2023-05-12 21:38:33 +02:00
|
|
|
{
|
|
|
|
|
const QDir parentDir(parentPath);
|
|
|
|
|
for (int i = 0; i < s_subFilesCount; ++i)
|
2023-05-19 11:53:44 +02:00
|
|
|
QFile::copy(templateFile, parentDir.filePath(fileName(i)));
|
2023-05-12 21:38:33 +02:00
|
|
|
|
|
|
|
|
if (depth == 0)
|
2023-05-19 11:53:44 +02:00
|
|
|
return true;
|
2023-05-12 21:38:33 +02:00
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
const QString originalDirName = dirName(0);
|
|
|
|
|
if (!parentDir.mkdir(originalDirName))
|
|
|
|
|
return false;
|
2023-05-12 21:38:33 +02:00
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
const QString originalDirPath = parentDir.filePath(originalDirName);
|
|
|
|
|
if (!generateOriginal(originalDirPath, templateFile, depth - 1))
|
|
|
|
|
return false;
|
2023-05-12 21:38:33 +02:00
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
for (int i = 1; i < s_subDirsCount; ++i) {
|
|
|
|
|
const FilePath source = FilePath::fromString(originalDirPath);
|
|
|
|
|
const FilePath destination = FilePath::fromString(parentDir.filePath(dirName(i)));
|
|
|
|
|
if (!source.copyRecursively(destination))
|
|
|
|
|
return false;
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
2023-05-19 11:53:44 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void generateCopy(QPromise<void> &promise, const QString &sourcePath,
|
|
|
|
|
const QString &destPath)
|
|
|
|
|
{
|
|
|
|
|
const FilePath source = FilePath::fromString(sourcePath);
|
|
|
|
|
const FilePath destination = FilePath::fromString(destPath);
|
|
|
|
|
if (!source.copyRecursively(destination))
|
|
|
|
|
promise.future().cancel();
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class tst_SubDirFileIterator : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
|
void initTestCase()
|
|
|
|
|
{
|
|
|
|
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
|
|
|
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
|
|
|
|
|
|
|
|
|
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
|
|
|
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
|
|
|
LauncherInterface::setPathToLauncher(libExecPath);
|
|
|
|
|
|
|
|
|
|
qDebug() << "This manual test compares the performance of the SubDirFileIterator with "
|
|
|
|
|
"a manually written iterator using QDir::entryInfoList().";
|
|
|
|
|
QTC_SCOPED_TIMER("GENERATING TEMPORARY FILES TREE");
|
2023-05-19 11:53:44 +02:00
|
|
|
m_threadsCount = threadsCount();
|
|
|
|
|
m_filesCount = expectedFilesCount();
|
|
|
|
|
m_dirsCount = expectedDirsCount();
|
2023-05-12 21:38:33 +02:00
|
|
|
m_tempDir.reset(new QTemporaryDir);
|
2023-05-19 11:53:44 +02:00
|
|
|
qDebug() << "Generating on" << m_threadsCount << "cores...";
|
2023-05-12 21:38:33 +02:00
|
|
|
qDebug() << "Generating" << m_filesCount << "files...";
|
|
|
|
|
qDebug() << "Generating" << m_dirsCount << "dirs...";
|
|
|
|
|
qDebug() << "Generating inside" << m_tempDir->path() << "dir...";
|
|
|
|
|
const QString templateFile = m_tempDir->filePath("fileTemplate.txt");
|
|
|
|
|
{
|
|
|
|
|
QFile file(templateFile);
|
|
|
|
|
QVERIFY(file.open(QIODevice::ReadWrite));
|
|
|
|
|
file.write("X");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QDir parentDir(m_tempDir->path());
|
2023-05-19 11:53:44 +02:00
|
|
|
const QString sourceDirName = dirName(0);
|
|
|
|
|
QVERIFY(parentDir.mkdir(sourceDirName));
|
|
|
|
|
QVERIFY(generateOriginal(parentDir.filePath(sourceDirName), templateFile, s_treeDepth));
|
|
|
|
|
|
|
|
|
|
const auto onCopySetup = [](const QString &sourcePath, const QString &destPath) {
|
|
|
|
|
return [sourcePath, destPath](Async<void> &async) {
|
|
|
|
|
async.setConcurrentCallData(generateCopy, sourcePath, destPath);
|
2023-05-12 21:38:33 +02:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-19 11:53:44 +02:00
|
|
|
// Parallelize tree generation
|
|
|
|
|
QList<TaskItem> tasks{parallelLimit(m_threadsCount)};
|
|
|
|
|
for (int i = 1; i < s_topLevelSubDirsCount; ++i) {
|
|
|
|
|
const QString destDirName = dirName(i);
|
|
|
|
|
QVERIFY(parentDir.mkdir(destDirName));
|
|
|
|
|
tasks.append(AsyncTask<void>(onCopySetup(parentDir.filePath(sourceDirName),
|
|
|
|
|
parentDir.filePath(destDirName))));
|
|
|
|
|
}
|
2023-05-12 21:38:33 +02:00
|
|
|
TaskTree taskTree(tasks);
|
|
|
|
|
QVERIFY(taskTree.runBlocking());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cleanupTestCase()
|
|
|
|
|
{
|
|
|
|
|
QTC_SCOPED_TIMER("CLEANING UP");
|
|
|
|
|
|
|
|
|
|
// Parallelize tree removal
|
|
|
|
|
const auto removeTree = [](const QString &parentPath) {
|
|
|
|
|
FilePath::fromString(parentPath).removeRecursively();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onSetup = [removeTree](const QString &parentPath) {
|
|
|
|
|
return [parentPath, removeTree](Async<void> &async) {
|
|
|
|
|
async.setConcurrentCallData(removeTree, parentPath);
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-05-19 11:53:44 +02:00
|
|
|
const QDir parentDir(m_tempDir->path());
|
|
|
|
|
QList<TaskItem> tasks {parallelLimit(m_threadsCount)};
|
|
|
|
|
for (int i = 0; i < s_topLevelSubDirsCount; ++i)
|
|
|
|
|
tasks.append(AsyncTask<void>(onSetup(parentDir.filePath(dirName(i)))));
|
2023-05-12 21:38:33 +02:00
|
|
|
|
|
|
|
|
TaskTree taskTree(tasks);
|
|
|
|
|
QVERIFY(taskTree.runBlocking());
|
|
|
|
|
|
|
|
|
|
m_tempDir.reset();
|
|
|
|
|
Singleton::deleteAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testSubDirFileIterator()
|
|
|
|
|
{
|
|
|
|
|
QTC_SCOPED_TIMER("ITERATING with SubDirFileIterator");
|
|
|
|
|
int filesCount = 0;
|
|
|
|
|
{
|
|
|
|
|
const FilePath root(FilePath::fromString(m_tempDir->path()));
|
|
|
|
|
SubDirFileIterator it({root}, {}, {});
|
|
|
|
|
auto i = it.begin();
|
|
|
|
|
const auto e = it.end();
|
|
|
|
|
while (i != e) {
|
|
|
|
|
++filesCount;
|
|
|
|
|
++i;
|
|
|
|
|
if (filesCount % 100000 == 0)
|
|
|
|
|
qDebug() << filesCount << '/' << m_filesCount << "files visited so far...";
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-19 11:53:44 +02:00
|
|
|
QCOMPARE(filesCount, m_filesCount);
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-23 17:34:59 +02:00
|
|
|
void testSubDirFileContainer()
|
|
|
|
|
{
|
|
|
|
|
QTC_SCOPED_TIMER("ITERATING with FileContainer");
|
|
|
|
|
int filesCount = 0;
|
|
|
|
|
{
|
|
|
|
|
const FilePath root(FilePath::fromString(m_tempDir->path()));
|
|
|
|
|
FileContainer container = SubDirFileContainer({root}, {}, {});
|
|
|
|
|
auto it = container.begin();
|
|
|
|
|
const auto end = container.end();
|
|
|
|
|
while (it != end) {
|
|
|
|
|
++filesCount;
|
|
|
|
|
++it;
|
|
|
|
|
if (filesCount % 100000 == 0)
|
|
|
|
|
qDebug() << filesCount << '/' << m_filesCount << "files visited so far...";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
QCOMPARE(filesCount, m_filesCount);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 21:38:33 +02:00
|
|
|
void testManualIterator()
|
|
|
|
|
{
|
|
|
|
|
QTC_SCOPED_TIMER("ITERATING with manual iterator");
|
|
|
|
|
int filesCount = 0;
|
|
|
|
|
int dirsCount = 0;
|
|
|
|
|
{
|
|
|
|
|
const QDir root(m_tempDir->path());
|
|
|
|
|
std::unordered_set<QString> visitedFiles;
|
|
|
|
|
std::unordered_set<QString> visitedDirs;
|
|
|
|
|
visitedDirs.emplace(root.absolutePath());
|
|
|
|
|
QFileInfoList workingList = root.entryInfoList(s_filters);
|
|
|
|
|
while (!workingList.isEmpty()) {
|
|
|
|
|
const QFileInfo fi = workingList.takeLast();
|
|
|
|
|
const QString absoluteFilePath = fi.absoluteFilePath();
|
|
|
|
|
if (fi.isDir()) {
|
|
|
|
|
if (!visitedDirs.emplace(absoluteFilePath).second)
|
|
|
|
|
continue;
|
|
|
|
|
if (fi.isSymbolicLink() && !visitedDirs.emplace(fi.symLinkTarget()).second)
|
|
|
|
|
continue;
|
|
|
|
|
++dirsCount;
|
|
|
|
|
workingList.append(QDir(absoluteFilePath).entryInfoList(s_filters));
|
|
|
|
|
} else {
|
|
|
|
|
if (!visitedFiles.emplace(absoluteFilePath).second)
|
|
|
|
|
continue;
|
|
|
|
|
if (fi.isSymbolicLink() && !visitedFiles.emplace(fi.symLinkTarget()).second)
|
|
|
|
|
continue;
|
|
|
|
|
++filesCount;
|
|
|
|
|
if (filesCount % 100000 == 0)
|
|
|
|
|
qDebug() << filesCount << '/' << m_filesCount << "files visited so far...";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-19 11:53:44 +02:00
|
|
|
QCOMPARE(filesCount, m_filesCount);
|
|
|
|
|
QCOMPARE(dirsCount, m_dirsCount);
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void testQDirIterator()
|
|
|
|
|
{
|
|
|
|
|
QTC_SCOPED_TIMER("ITERATING with QDirIterator");
|
|
|
|
|
int filesCount = 0;
|
|
|
|
|
int dirsCount = 0;
|
|
|
|
|
{
|
|
|
|
|
QDirIterator it(m_tempDir->path(), s_filters, QDirIterator::Subdirectories
|
|
|
|
|
| QDirIterator::FollowSymlinks);
|
|
|
|
|
while (it.hasNext()) {
|
2023-05-19 16:35:29 +02:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
|
2023-05-12 21:38:33 +02:00
|
|
|
const QFileInfo fi = it.nextFileInfo();
|
2023-05-19 16:35:29 +02:00
|
|
|
#else
|
|
|
|
|
it.next();
|
|
|
|
|
const QFileInfo fi = it.fileInfo();
|
|
|
|
|
#endif
|
2023-05-12 21:38:33 +02:00
|
|
|
if (fi.isDir()) {
|
|
|
|
|
++dirsCount;
|
|
|
|
|
} else {
|
|
|
|
|
++filesCount;
|
|
|
|
|
if (filesCount % 100000 == 0)
|
|
|
|
|
qDebug() << filesCount << '/' << m_filesCount << "files visited so far...";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-19 11:53:44 +02:00
|
|
|
QCOMPARE(filesCount, m_filesCount);
|
|
|
|
|
QCOMPARE(dirsCount, m_dirsCount);
|
2023-05-12 21:38:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2023-05-19 11:53:44 +02:00
|
|
|
int m_threadsCount = 1;
|
2023-05-12 21:38:33 +02:00
|
|
|
int m_dirsCount = 0;
|
|
|
|
|
int m_filesCount = 0;
|
|
|
|
|
std::unique_ptr<QTemporaryDir> m_tempDir;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QTEST_GUILESS_MAIN(tst_SubDirFileIterator)
|
|
|
|
|
|
|
|
|
|
#include "tst_subdirfileiterator.moc"
|