File indexing completed on 2024-05-19 15:45:49
0001 /* 0002 SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "test_projectload.h" 0008 0009 #include <QTest> 0010 #include <QSignalSpy> 0011 #include <QProcess> 0012 #include <QTemporaryDir> 0013 #include <QDebug> 0014 #include <QRandomGenerator> 0015 0016 #include <tests/autotestshell.h> 0017 #include <tests/testcore.h> 0018 0019 #include <interfaces/icore.h> 0020 #include <interfaces/iprojectcontroller.h> 0021 #include <interfaces/iproject.h> 0022 #include <interfaces/ilanguagecontroller.h> 0023 0024 #include <project/interfaces/iprojectfilemanager.h> 0025 #include <project/projectmodel.h> 0026 #include <language/backgroundparser/backgroundparser.h> 0027 0028 #include <KIO/Global> 0029 0030 QTEST_MAIN(TestProjectLoad) 0031 0032 using namespace KDevelop; 0033 0034 namespace { 0035 0036 struct TestProject 0037 { 0038 // temp directory of project 0039 QTemporaryDir* dir; 0040 // name of the project (random) 0041 QString name; 0042 // project file (*.kdev4) 0043 QUrl file; 0044 ~TestProject() { 0045 IProject* p = ICore::self()->projectController()->findProjectByName(name); 0046 if (p) { 0047 ICore::self()->projectController()->closeProject(p); 0048 } 0049 delete dir; 0050 } 0051 }; 0052 0053 TestProject makeProject() 0054 { 0055 TestProject ret; 0056 ret.dir = new QTemporaryDir(); 0057 QFileInfo dir(ret.dir->path()); 0058 Q_ASSERT(dir.exists()); 0059 ret.name = dir.fileName(); 0060 0061 QStringList projectFileContents; 0062 projectFileContents 0063 << QStringLiteral("[Project]") 0064 << QStringLiteral("Name=") + ret.name 0065 << QStringLiteral("Manager=KDevGenericManager"); 0066 0067 QUrl projecturl = QUrl::fromLocalFile( dir.absoluteFilePath() + "/simpleproject.kdev4" ); 0068 QFile projectFile(projecturl.toLocalFile()); 0069 projectFile.open(QIODevice::WriteOnly); 0070 projectFile.write(projectFileContents.join(QLatin1Char('\n')).toLatin1()); 0071 projectFile.close(); 0072 ret.file = projecturl; 0073 0074 Q_ASSERT(ret.dir->isValid()); 0075 Q_ASSERT(projecturl.adjusted(QUrl::RemoveFilename).toLocalFile() == ret.dir->path() + '/'); 0076 0077 return ret; 0078 } 0079 0080 bool createFile(const QString& path) 0081 { 0082 QFile f(path); 0083 if (!f.open(QIODevice::WriteOnly)) { 0084 qWarning() << f.errorString() << path; 0085 return false; 0086 } 0087 0088 auto* randomGenerator = QRandomGenerator::global(); 0089 for (int i = 0; i < 4; ++i) { 0090 f.write(QByteArray::number(randomGenerator->generate())); 0091 } 0092 0093 if (!f.flush()) { 0094 qWarning() << f.errorString() << path; 0095 return false; 0096 } 0097 0098 f.close(); 0099 return true; 0100 } 0101 0102 bool writeRandomStructure(QString path, int files) 0103 { 0104 QDir p(path); 0105 const QString name = QString::number(QRandomGenerator::global()->generate()); 0106 if (QRandomGenerator::global()->bounded(5) < 1) { 0107 if (!p.mkdir(name)) { 0108 return false; 0109 } 0110 0111 //qDebug() << "wrote path" << path; 0112 path += '/' + name; 0113 } else { 0114 if (!createFile(path+'/'+name)) { 0115 return false; 0116 } 0117 0118 //qDebug() << "wrote file" << path+"/"+name; 0119 } 0120 files--; 0121 if (files > 0) { 0122 if (!writeRandomStructure(path, files)) { 0123 return false; 0124 } 0125 } 0126 return true; 0127 } 0128 0129 bool fillProject(int filesPerDir, int dirs, const TestProject& project, bool wait) 0130 { 0131 for(int i=0; i < dirs; ++i) { 0132 const QString name = "foox" + QString::number(i); 0133 if (!QDir(project.dir->path()).mkdir(name)) { 0134 return false; 0135 } 0136 0137 if (!writeRandomStructure(project.dir->path() + "/" + name, filesPerDir)) { 0138 return false; 0139 } 0140 0141 if (wait) { 0142 QTest::qWait(100); 0143 } 0144 } 0145 return true; 0146 } 0147 } 0148 0149 void TestProjectLoad::initTestCase() 0150 { 0151 AutoTestShell::init({QStringLiteral("KDevGenericManager")}); 0152 TestCore::initialize(); 0153 ICore::self()->languageController()->backgroundParser()->disableProcessing(); 0154 0155 qRegisterMetaType<IProject*>(); 0156 0157 const auto projects = ICore::self()->projectController()->projects(); 0158 for (IProject* p : projects) { 0159 ICore::self()->projectController()->closeProject(p); 0160 } 0161 } 0162 0163 void TestProjectLoad::cleanupTestCase() 0164 { 0165 TestCore::shutdown(); 0166 } 0167 0168 void TestProjectLoad::init() 0169 { 0170 const auto projects = ICore::self()->projectController()->projects(); 0171 for (IProject* p : projects) { 0172 ICore::self()->projectController()->closeProject(p); 0173 } 0174 QCOMPARE(ICore::self()->projectController()->projects().size(), 0); 0175 } 0176 0177 void TestProjectLoad::addRemoveFiles() 0178 { 0179 const TestProject p = makeProject(); 0180 0181 createFile(p.dir->path()+"/sdf"); 0182 0183 ICore::self()->projectController()->openProject(p.file); 0184 QTRY_COMPARE(ICore::self()->projectController()->projects().size(), 1); 0185 IProject* project = ICore::self()->projectController()->projects().first(); 0186 QCOMPARE(project->projectFile().toUrl(), p.file); 0187 0188 //KDirWatch adds/removes the file automatically 0189 for (int i=0; i<100; ++i) { 0190 createFile(p.dir->path()+"/blub"+QString::number(i)); 0191 } 0192 for (int i=0; i<50; ++i) { 0193 QFile::remove(p.dir->path()+"/blub"+QString::number(i)); 0194 } 0195 QTRY_COMPARE(project->projectItem()->fileList().count(), 51); 0196 0197 QUrl url = QUrl::fromLocalFile(p.dir->path()+"/blub"+QString::number(50)).adjusted(QUrl::NormalizePathSegments); 0198 QCOMPARE(project->filesForPath(IndexedString(url)).count(), 1); 0199 ProjectFileItem* file = project->filesForPath(IndexedString(url)).at(0); 0200 project->projectFileManager()->removeFilesAndFolders(QList<ProjectBaseItem*>() << file ); //message box has to be accepted manually :( 0201 QTRY_COMPARE(project->projectItem()->fileList().count(), 50); 0202 0203 for (int i=51; i<100; ++i) { 0204 QFile::remove(p.dir->path()+"/blub"+QString::number(i)); 0205 } 0206 0207 QTRY_COMPARE(project->projectItem()->fileList().count(), 1); 0208 } 0209 0210 void TestProjectLoad::removeDirRecursive() 0211 { 0212 const TestProject p = makeProject(); 0213 0214 createFile(p.dir->path()+"/sdf"); 0215 { 0216 QDir d(p.dir->path()); 0217 QVERIFY(d.mkdir(QStringLiteral("blub"))); 0218 QVERIFY(d.cd(QStringLiteral("blub"))); 0219 for (int i=0; i<10; ++i) { 0220 createFile(d.filePath("file"+QString::number(i))); 0221 } 0222 } 0223 0224 QVERIFY(ICore::self()->projectController()->projects().isEmpty()); 0225 0226 ICore::self()->projectController()->openProject(p.file); 0227 QTRY_COMPARE(ICore::self()->projectController()->projects().size(), 1); 0228 IProject* project = ICore::self()->projectController()->projects().first(); 0229 QCOMPARE(project->projectFile().toUrl(), p.file); 0230 0231 for (int i=0; i<1; ++i) { 0232 QUrl url = QUrl::fromLocalFile(p.dir->path()+"/blub").adjusted(QUrl::NormalizePathSegments); 0233 QCOMPARE(project->foldersForPath(IndexedString(url)).count(), 1); 0234 0235 ProjectFolderItem* file = project->foldersForPath(IndexedString(url)).at(0); 0236 project->projectFileManager()->removeFilesAndFolders(QList<ProjectBaseItem*>() << file ); 0237 } 0238 0239 QTRY_COMPARE(project->projectItem()->fileList().count(), 1); 0240 } 0241 0242 void TestProjectLoad::addLotsOfFiles() 0243 { 0244 TestProject p = makeProject(); 0245 0246 ICore::self()->projectController()->openProject(p.file); 0247 QTRY_COMPARE(ICore::self()->projectController()->projects().size(), 1); 0248 IProject* project = ICore::self()->projectController()->projects().first(); 0249 QCOMPARE(project->projectFile().toUrl(), p.file); 0250 0251 QVERIFY(fillProject(50, 25, p, true)); 0252 0253 QTest::qWait(2000); 0254 } 0255 0256 void TestProjectLoad::addMultipleJobs() 0257 { 0258 const TestProject p1 = makeProject(); 0259 QVERIFY(fillProject(10, 25, p1, false)); 0260 const TestProject p2 = makeProject(); 0261 QVERIFY(fillProject(10, 25, p2, false)); 0262 0263 ICore::self()->projectController()->openProject(p1.file); 0264 ICore::self()->projectController()->openProject(p2.file); 0265 0266 QTRY_COMPARE(ICore::self()->projectController()->projects().size(), 2); 0267 } 0268 0269 void TestProjectLoad::raceJob() 0270 { 0271 // our goal here is to try to reproduce https://bugs.kde.org/show_bug.cgi?id=260741 0272 // my idea is that this can be triggered by the following: 0273 // - list dir foo/bar containing lots of files 0274 // - remove dir foo while listjob is still running 0275 TestProject p = makeProject(); 0276 QDir dir(p.dir->path()); 0277 QVERIFY(dir.mkpath(QStringLiteral("test/zzzzz"))); 0278 for(int i = 0; i < 1000; ++i) { 0279 createFile(QString(p.dir->path() + "/test/zzzzz/%1").arg(i)); 0280 createFile(QString(p.dir->path() + "/test/%1").arg(i)); 0281 } 0282 0283 ICore::self()->projectController()->openProject(p.file); 0284 QTRY_COMPARE(ICore::self()->projectController()->projectCount(), 1); 0285 IProject *project = ICore::self()->projectController()->projectAt(0); 0286 QCOMPARE(project->projectFile().toUrl(), p.file); 0287 ProjectFolderItem* root = project->projectItem(); 0288 QCOMPARE(root->project(), project); 0289 QVERIFY(root->model()); 0290 QCOMPARE(root->rowCount(), 1); 0291 ProjectBaseItem* testItem = root->child(0); 0292 QVERIFY(testItem->folder()); 0293 QCOMPARE(testItem->baseName(), QStringLiteral("test")); 0294 QCOMPARE(testItem->rowCount(), 1001); 0295 int last = testItem->children().size() - 1; 0296 ProjectBaseItem* asdfItem = testItem->children().at(last); 0297 QVERIFY(asdfItem->folder()); 0298 0299 // reload to trigger new list job 0300 project->projectFileManager()->reload(testItem->folder()); 0301 // move dir 0302 QVERIFY(dir.rename(QStringLiteral("test"), QStringLiteral("test2"))); 0303 // move sub dir 0304 QVERIFY(dir.rename(QStringLiteral("test2/zzzzz"), QStringLiteral("test2/bla"))); 0305 0306 QTRY_COMPARE(root->rowCount() == 1 ? root->child(0)->baseName() : QString(), QStringLiteral("test2")); 0307 0308 // reload full model and then move dir 0309 project->reloadModel(); 0310 QVERIFY(dir.rename(QStringLiteral("test2"), QStringLiteral("test3"))); 0311 0312 // note: this actually invalidates the root, so query that again 0313 root = project->projectItem(); 0314 QVERIFY(root); 0315 0316 QTRY_COMPARE(root->rowCount() == 1 ? root->child(0)->baseName() : QString(), QStringLiteral("test3")); 0317 } 0318 0319 void TestProjectLoad::addDuringImport() 0320 { 0321 // our goal here is to try to reproduce an issue in the optimized filesForPath implementation 0322 // which requires the project to be associated to the model to function properly 0323 // to trigger this we create a big project, import it and then call filesForPath during 0324 // the import action 0325 TestProject p = makeProject(); 0326 QDir dir(p.dir->path()); 0327 QVERIFY(dir.mkpath(QStringLiteral("test/zzzzz"))); 0328 for(int i = 0; i < 1000; ++i) { 0329 createFile(QString(p.dir->path() + "/test/zzzzz/%1").arg(i)); 0330 createFile(QString(p.dir->path() + "/test/%1").arg(i)); 0331 } 0332 0333 QSignalSpy spy(ICore::self()->projectController(), 0334 SIGNAL(projectAboutToBeOpened(KDevelop::IProject*))); 0335 ICore::self()->projectController()->openProject(p.file); 0336 // not yet ready 0337 QCOMPARE(ICore::self()->projectController()->projectCount(), 0); 0338 // but about to be opened 0339 QCOMPARE(spy.count(), 1); 0340 auto* project = spy.value(0).at(0).value<IProject*>(); 0341 QVERIFY(project); 0342 QCOMPARE(project->path(), Path(KIO::upUrl(p.file))); 0343 QUrl file = p.file.resolved(QUrl(QStringLiteral("test/zzzzz/999"))); 0344 QVERIFY(QFile::exists(file.toLocalFile())); 0345 // this most probably is not yet loaded 0346 // and this should not crash 0347 QCOMPARE(project->itemsForPath(IndexedString(file)).size(), 0); 0348 // now delete that file and don't crash 0349 QFile::remove(file.toLocalFile()); 0350 // now create another file 0351 QUrl file2 = file.adjusted(QUrl::RemoveFilename); 0352 file2.setPath(file2.path() + "999v2"); 0353 createFile(file2.toLocalFile()); 0354 QVERIFY(!project->isReady()); 0355 // now wait for finish 0356 QTRY_VERIFY(project->isReady()); 0357 // make sure our file removal + addition was properly tracked 0358 QCOMPARE(project->filesForPath(IndexedString(file)).size(), 0); 0359 QCOMPARE(project->filesForPath(IndexedString(file2)).size(), 1); 0360 0361 //NOTE: this test is probably incomplete, I bet there are some race conditions left, 0362 // esp. when adding a file at a point where the parent folder was already imported 0363 // or removing a file that was already imported 0364 } 0365 0366 #include "moc_test_projectload.cpp"