/* This file is part of KDevelop Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "reloadtest.h" #include #include #include #include #include #include #include #include #include #include #include #include QTEST_KDEMAIN(ProjectLoadTest, GUI) Q_DECLARE_METATYPE(KDevelop::IProject*); ///FIXME: get rid of this, use temporary dir+file classes! void exec(const QString &cmd) { QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(cmd); proc.waitForFinished(); Q_ASSERT(proc.exitStatus() == QProcess::NormalExit); Q_ASSERT(proc.exitStatus() == 0); } void ProjectLoadTest::initTestCase() { KDevelop::AutoTestShell::init(); KDevelop::TestCore::initialize(); KDevelop::ICore::self()->languageController()->backgroundParser()->disableProcessing(); qRegisterMetaType("KDevelop::IProject*"); foreach(KDevelop::IProject* p, KDevelop::ICore::self()->projectController()->projects()) { KDevelop::ICore::self()->projectController()->closeProject(p); } } void ProjectLoadTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void ProjectLoadTest::init() { Q_ASSERT(KDevelop::ICore::self()->projectController()->projects().isEmpty()); } struct TestProject { // temp directory of project KTempDir* dir; // name of the project (random) QString name; // project file (*.kdev4) KUrl file; ~TestProject() { KDevelop::IProject* p = KDevelop::ICore::self()->projectController()->findProjectByName(name); if (p) { KDevelop::ICore::self()->projectController()->closeProject(p); } delete dir; } }; TestProject makeProject() { TestProject ret; ret.dir = new KTempDir(); QFileInfo dir(ret.dir->name().left(ret.dir->name().length() - 1)); Q_ASSERT(dir.exists()); ret.name = dir.fileName(); QStringList projectFileContents; projectFileContents << "[Project]" << QString("Name=") + ret.name << "Manager=KDevGenericManager"; KUrl projecturl( dir.absoluteFilePath() + "/simpleproject.kdev4" ); QFile projectFile(projecturl.toLocalFile()); projectFile.open(QIODevice::WriteOnly); projectFile.write(projectFileContents.join("\n").toAscii()); projectFile.close(); ret.file = projecturl; Q_ASSERT(ret.dir->exists()); Q_ASSERT(projecturl.upUrl().toLocalFile() == ret.dir->name()); return ret; } void ProjectLoadTest::addRemoveFiles() { const TestProject p = makeProject(); QFile f(p.dir->name()+"/sdf"); f.open(QIODevice::WriteOnly); f.close(); KDevelop::ICore::self()->projectController()->openProject(p.file); QVERIFY(QTest::kWaitForSignal(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), 2000)); KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFileUrl(), p.file); //KDirWatch adds/removes the file automatically for (int i=0; i<100; ++i) { QFile f2(p.dir->name()+"/blub"+QString::number(i)); f2.open(QIODevice::WriteOnly); f2.close(); } for (int i=0; i<50; ++i) { QFile f2(p.dir->name()+"/blub"+QString::number(i)); QVERIFY(f2.exists()); f2.remove(); } QTest::qWait(500); KUrl url(p.dir->name()+"/blub"+QString::number(50)); url.cleanPath(); QCOMPARE(project->filesForUrl(url).count(), 1); KDevelop::ProjectFileItem* file = project->filesForUrl(url).first(); project->projectFileManager()->removeFilesAndFolders(QList() << file ); //message box has to be accepted manually :( for (int i=51; i<100; ++i) { QFile f2(p.dir->name()+"/blub"+QString::number(i)); f2.remove(); } QTest::qWait(2000); QCOMPARE(project->fileCount(), 1); } void ProjectLoadTest::removeDirRecursive() { const TestProject p = makeProject(); { QFile f(p.dir->name()+"/sdf"); f.open(QIODevice::WriteOnly); f.close(); } { QDir(p.dir->name()).mkdir("blub"); for (int i=0; i<10; ++i) { QFile f(p.dir->name()+"/blub/file"+QString::number(i)); f.open(QIODevice::WriteOnly); f.close(); } } //close previously opened projects QTest::qWait(1000); //wait for projects to load foreach ( KDevelop::IProject* p, KDevelop::ICore::self()->projectController()->projects()) { KDevelop::ICore::self()->projectController()->closeProject(p); QTest::qWait(100); } QVERIFY(KDevelop::ICore::self()->projectController()->projects().isEmpty()); KDevelop::ICore::self()->projectController()->openProject(p.file); QVERIFY(QTest::kWaitForSignal(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), 20000)); KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFileUrl(), p.file); for (int i=0; i<1; ++i) { KUrl url(p.dir->name()+"/blub"); url.cleanPath(); QCOMPARE(project->foldersForUrl(url).count(), 1); KDevelop::ProjectFolderItem* file = project->foldersForUrl(url).first(); project->projectFileManager()->removeFilesAndFolders(QList() << file ); } QTest::qWait(2000); QCOMPARE(project->fileCount(), 1); } void createFile(const QString& path) { QFile f(path); f.open(QIODevice::WriteOnly); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.write(QByteArray::number(qrand())); f.close(); } void _writeRandomStructure(QString path, int files) { QDir p(path); QString name = QString::number(qrand()); if (qrand() < RAND_MAX / 5) { p.mkdir(name); path += '/' + name; // kDebug() << "wrote path" << path; } else { createFile(path+'/'+name); // kDebug() << "wrote file" << path+"/"+name; } files--; if (files > 0) { _writeRandomStructure(path, files); } } void fillProject(int filesPerDir, int dirs, const TestProject& project, bool wait) { for(int i=0; i < dirs; ++i) { const QString name = "foox" + QString::number(i); QDir(project.dir->name()).mkdir(name); _writeRandomStructure(project.dir->name() + name, filesPerDir); if (wait) { QTest::qWait(100); } } } void ProjectLoadTest::addLotsOfFiles() { TestProject p = makeProject(); KDevelop::ICore::self()->projectController()->openProject(p.file); QVERIFY(QTest::kWaitForSignal(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), 2000)); QCOMPARE(KDevelop::ICore::self()->projectController()->projects().size(), 1); KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->projects().first(); QCOMPARE(project->projectFileUrl(), p.file); fillProject(50, 25, p, true); QTest::qWait(2000); } void ProjectLoadTest::addMultipleJobs() { const TestProject p1 = makeProject(); fillProject(10, 25, p1, false); const TestProject p2 = makeProject(); fillProject(10, 25, p2, false); QSignalSpy spy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); KDevelop::ICore::self()->projectController()->openProject(p1.file); KDevelop::ICore::self()->projectController()->openProject(p2.file); const int wait = 25; const int maxWait = 2000; int waited = 0; while(waited < maxWait && spy.count() != 2) { QTest::qWait(wait); waited += wait; } QCOMPARE(KDevelop::ICore::self()->projectController()->projects().size(), 2); } void ProjectLoadTest::raceJob() { // our goal here is to try to reproduce https://bugs.kde.org/show_bug.cgi?id=260741 // my idea is that this can be triggered by the following: // - list dir foo/bar containing lots of files // - remove dir foo while listjob is still running TestProject p = makeProject(); QDir dir(p.dir->name()); QVERIFY(dir.mkpath("test/zzzzz")); for(int i = 0; i < 1000; ++i) { createFile(QString(p.dir->name() + "/test/zzzzz/%1").arg(i)); createFile(QString(p.dir->name() + "/test/%1").arg(i)); } KDevelop::ICore::self()->projectController()->openProject(p.file); QVERIFY(QTest::kWaitForSignal(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), 2000)); QCOMPARE(KDevelop::ICore::self()->projectController()->projectCount(), 1); KDevelop::IProject *project = KDevelop::ICore::self()->projectController()->projectAt(0); QCOMPARE(project->projectFileUrl(), p.file); KDevelop::ProjectFolderItem* root = project->projectItem(); QCOMPARE(root->rowCount(), 1); KDevelop::ProjectBaseItem* testItem = root->child(0); QVERIFY(testItem->folder()); QCOMPARE(testItem->baseName(), QString("test")); QCOMPARE(testItem->rowCount(), 1001); KDevelop::ProjectBaseItem* asdfItem = testItem->children().last(); QVERIFY(asdfItem->folder()); // move dir dir.rename("test", "test2"); // move sub dir dir.rename("test2/zzzzz", "test2/bla"); QTest::qWait(500); QCOMPARE(root->rowCount(), 1); testItem = root->child(0); QVERIFY(testItem->folder()); QCOMPARE(testItem->baseName(), QString("test2")); } void ProjectLoadTest::addDuringImport() { // our goal here is to try to reproduce an issue in the optimized filesForUrl implementation // which requires the project to be associated to the model to function properly // to trigger this we create a big project, import it and then call filesForUrl during // the import action TestProject p = makeProject(); QDir dir(p.dir->name()); QVERIFY(dir.mkpath("test/zzzzz")); for(int i = 0; i < 1000; ++i) { createFile(QString(p.dir->name() + "/test/zzzzz/%1").arg(i)); createFile(QString(p.dir->name() + "/test/%1").arg(i)); } QSignalSpy spy(KDevelop::ICore::self()->projectController(), SIGNAL(projectAboutToBeOpened(KDevelop::IProject*))); KDevelop::ICore::self()->projectController()->openProject(p.file); // not yet ready QCOMPARE(KDevelop::ICore::self()->projectController()->projectCount(), 0); // but about to be opened QCOMPARE(spy.count(), 1); KDevelop::IProject* project = spy.value(0).first().value(); QVERIFY(project); QCOMPARE(project->folder(), p.file.upUrl()); KUrl file(p.file, "test/zzzzz/999"); QVERIFY(QFile::exists(file.toLocalFile())); // this most probably is not yet loaded // and this should not crash QCOMPARE(project->itemsForUrl(file).size(), 0); // now delete that file and don't crash QFile::remove(file.toLocalFile()); // now create another file KUrl file2 = file; file2.setFileName("999v2"); createFile(file2.toLocalFile()); QVERIFY(!project->isReady()); // now wait for finish QVERIFY(QTest::kWaitForSignal(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), 2000)); QVERIFY(project->isReady()); // make sure our file removal + addition was properly tracked QCOMPARE(project->filesForUrl(file).size(), 0); QCOMPARE(project->filesForUrl(file2).size(), 1); //NOTE: this test is probabably incomplete, I bet there are some race conditions left, // esp. when adding a file at a point where the parent folder was already imported // or removing a file that was already imported }