/*************************************************************************** * Copyright 2008 Manuel Breugelmans * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectcontrollertest.h" #include #include #include #include #include #include #include #include #include "../core.h" #include "../projectcontroller.h" #include "../project.h" #include "../../interfaces/iplugin.h" #include "../../project/interfaces/iprojectfilemanager.h" #include "../project/projectmodel.h" using namespace KDevelop; using QTest::kWaitForSignal; Q_DECLARE_METATYPE(KDevelop::IProject*) namespace { class DialogProviderFake : public IProjectDialogProvider { Q_OBJECT public: DialogProviderFake() : m_reopen(true) {} virtual ~DialogProviderFake() {} bool m_reopen; public slots: virtual KUrl askProjectConfigLocation(const KUrl& startUrl) { return KUrl(); } virtual bool userWantsReopen() { return m_reopen; } }; } /*! A Filemanager plugin that allows you to setup a file & directory structure */ class FakeFileManager : public IPlugin, public IProjectFileManager { Q_OBJECT Q_INTERFACES(KDevelop::IProjectFileManager) public: FakeFileManager(QObject*, const QVariantList&) : IPlugin(KGlobal::mainComponent(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } FakeFileManager() : IPlugin(KGlobal::mainComponent(), Core::self()) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) } virtual ~FakeFileManager() {} virtual Features features() const { return IProjectFileManager::Files | IProjectFileManager::Folders; } QMap m_filesInFolder; // initialize QMap m_subFoldersInFolder; /*! Setup this manager such that @p folder contains @p file */ void addFileToFolder(KUrl folder, KUrl file) { if (!m_filesInFolder.contains(folder)) { m_filesInFolder[folder] = KUrl::List(); } m_filesInFolder[folder] << file; } /*! Setup this manager such that @p folder has @p subFolder */ void addSubFolderTo(KUrl folder, KUrl subFolder) { if (!m_subFoldersInFolder.contains(folder)) { m_subFoldersInFolder[folder] = KUrl::List(); } m_subFoldersInFolder[folder] << subFolder; } virtual QList parse(ProjectFolderItem *dom) { KUrl::List files = m_filesInFolder[dom->url()]; foreach (const KUrl& file, files) { new ProjectFileItem(dom->project(), file, dom); } KUrl::List folderUrls = m_subFoldersInFolder[dom->url()]; QList folders; foreach (const KUrl& folderUrl, folderUrls) { folders << new ProjectFolderItem(dom->project(), folderUrl, dom); } return folders; } virtual ProjectFolderItem *import(IProject *project) { ProjectFolderItem* it = new ProjectFolderItem(project, project->folder()); it->setProjectRoot(true); return it; } virtual ProjectFolderItem* addFolder(const KUrl& folder, ProjectFolderItem *parent) { return 0; } virtual ProjectFileItem* addFile(const KUrl& folder, ProjectFolderItem *parent) { return 0; } virtual bool removeFolder(ProjectFolderItem *folder) { return false; } virtual bool removeFile(ProjectFileItem *file) { return false; } virtual bool renameFile(ProjectFileItem* oldFile, const KUrl& newFile) { return false; } virtual bool renameFolder(ProjectFolderItem* oldFolder, const KUrl& newFolder ) { return false; } virtual bool reload(ProjectBaseItem* item) { return false; } }; K_PLUGIN_FACTORY(FakeFileManagerFactory, registerPlugin(); ) K_EXPORT_PLUGIN(FakeFileManager()) ////////////////////// Fixture /////////////////////////////////////////////// void ProjectControllerTest::initTestCase() { AutoTestShell::init(); TestCore::initialize(); qRegisterMetaType(); m_core = Core::self(); m_scratchDir = QDir(QDir::tempPath()); m_scratchDir.mkdir("prjctrltest"); m_scratchDir.cd("prjctrltest"); } void ProjectControllerTest::cleanupTestCase() { TestCore::shutdown(); } void ProjectControllerTest::init() { m_projName = "foo"; m_projFileUrl = writeProjectConfig(m_projName); m_projCtrl = m_core->projectControllerInternal(); m_tmpConfigs << m_projFileUrl; m_projFolder = KUrl(m_scratchDir.absolutePath() + '/'); } void ProjectControllerTest::cleanup() { // also close any opened projects as we do not get a clean fixture, // following tests should start off clean. foreach(IProject* p, m_projCtrl->projects()) { m_projCtrl->closeProject(p); } foreach(const KUrl &cfg, m_tmpConfigs) { QFile::remove(cfg.pathOrUrl()); } qDeleteAll(m_fileManagerGarbage); m_fileManagerGarbage.clear(); } ////////////////////// Commands ////////////////////////////////////////////// #define WAIT_FOR_OPEN_SIGNAL \ {\ bool gotSignal = kWaitForSignal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)), 30000);\ QVERIFY2(gotSignal, "Timeout while waiting for opened signal");\ } void(0) void ProjectControllerTest::openProject() { QSignalSpy* spy = createOpenedSpy(); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj);QVERIFY(proj); assertSpyCaughtProject(spy, proj); QCOMPARE(proj->projectFileUrl(), m_projFileUrl); QCOMPARE(proj->folder(), KUrl(m_scratchDir.absolutePath()+'/')); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); } void ProjectControllerTest::closeProject() { QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; IProject* proj = m_projCtrl->findProjectByName(m_projName); Q_ASSERT(proj); QSignalSpy* spy1 = createClosedSpy(); QSignalSpy* spy2 = createClosingSpy(); m_projCtrl->closeProject(proj); QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 0); assertProjectClosed(proj); assertSpyCaughtProject(spy1, proj); assertSpyCaughtProject(spy2, proj); } void ProjectControllerTest::openCloseOpen() { QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; IProject* proj; assertProjectOpened(m_projName, proj); QVERIFY(m_projCtrl->closeProject(proj)); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QCOMPARE(m_projCtrl->projectCount(), 1); assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::reopen() { m_projCtrl->setDialogProvider(new DialogProviderFake); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 1); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::reopenWhileLoading() { // Open the same project again while the first is still // loading. The second open request should be blocked. m_projCtrl->setDialogProvider(new DialogProviderFake); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); QVERIFY(!m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; // wait a bit for a second signal, this should timeout QVERIFY2(!kWaitForSignal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)), 100), "Received 2 projectOpened signals."); QCOMPARE(m_projCtrl->projectCount(), 1); IProject* proj; assertProjectOpened(m_projName, proj); assertSpyCaughtProject(spy, proj); } void ProjectControllerTest::openMultiple() { QString secondProj("bar"); KUrl secondCfgUrl = writeProjectConfig(secondProj); QSignalSpy* spy = createOpenedSpy(); QVERIFY(m_projCtrl->openProject(m_projFileUrl)); WAIT_FOR_OPEN_SIGNAL; QVERIFY(m_projCtrl->openProject(secondCfgUrl)); WAIT_FOR_OPEN_SIGNAL; QCOMPARE(m_projCtrl->projectCount(), 2); IProject *proj1, *proj2; assertProjectOpened(m_projName, proj1); assertProjectOpened(secondProj, proj2); QVERIFY(m_projCtrl->isProjectNameUsed(m_projName)); QVERIFY(m_projCtrl->isProjectNameUsed("bar")); QCOMPARE(spy->size(), 2); IProject* emittedProj1 = (*spy)[0][0].value(); IProject* emittedProj2 = (*spy)[1][0].value(); QCOMPARE(emittedProj1, proj1); QCOMPARE(emittedProj2, proj2); m_tmpConfigs << secondCfgUrl; } /*! Verify that the projectmodel contains a single project. Put this project's * ProjectFolderItem in the output parameter @p RootItem */ #define ASSERT_SINGLE_PROJECT_IN_MODEL(rootItem) \ {\ QCOMPARE(1,m_projCtrl->projectModel()->rowCount()); \ QModelIndex projIndex = m_projCtrl->projectModel()->index(0,0); \ QVERIFY(projIndex.isValid()); \ ProjectBaseItem* i = m_projCtrl->projectModel()->item( projIndex ); \ QVERIFY(i); \ QVERIFY(i->folder()); \ rootItem = i->folder();\ } void(0) /*! Verify that the projectitem @p item has a single child item * named @p name with url @p url. @p subFolder is an output parameter * that contains the sub-folder projectitem. */ #define ASSERT_SINGLE_SUBFOLDER_IN(item, name, url__, subFolder) \ {\ QCOMPARE(1,item->rowCount());\ QCOMPARE(1, item->folderList().size());\ ProjectFolderItem* fo = item->folderList()[0];\ QVERIFY(fo);\ QCOMPARE(url__, fo->url());\ QCOMPARE(QString(name), fo->data(Qt::DisplayRole).toString());\ subFolder = fo;\ } void(0) #define ASSERT_SINGLE_FILE_IN(rootFolder, name, url__, fileItem)\ {\ QCOMPARE(1,rootFolder->rowCount());\ QCOMPARE(1, rootFolder->fileList().size());\ ProjectFileItem* fi__ = rootFolder->fileList()[0];\ QVERIFY(fi__);\ QCOMPARE(url__, fi__->url());\ QCOMPARE(QString(name), fi__->data(Qt::DisplayRole).toString());\ fileItem = fi__;\ } void(0) // command void ProjectControllerTest::emptyProject() { // verify that the project model contains a single top-level folder after loading // an empty project assertEmptyProjectModel(); Project* proj = new Project(); FakeFileManager* fileMng = createFileManager(); Q_ASSERT(fileMng); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project is empty QCOMPARE(0,rootFolder->rowCount()); QCOMPARE(m_projName, rootFolder->data(Qt::DisplayRole).toString()); QCOMPARE(m_projFolder, rootFolder->url()); } // command void ProjectControllerTest::singleFile() { // verify that the project model contains a single file in the // top folder. First setup a FakeFileManager with this file Project* proj = new Project(); FakeFileManager* fileMng = createFileManager(); proj->setManagerPlugin(fileMng); KUrl fileUrl = KUrl(m_projFolder, "foobar"); fileMng->addFileToFolder(m_projFolder, fileUrl); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ProjectFileItem* fi; ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", fileUrl, fi); QCOMPARE(0,fi->rowCount()); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", fileUrl, fi); } // command void ProjectControllerTest::singleDirectory() { // verify that the project model contains a single folder in the // top folder. First setup a FakeFileManager with this folder Project* proj = new Project(); KUrl folderUrl = KUrl(m_projFolder, "foobar/"); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderUrl); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); // check that the project contains a single subfolder ProjectFolderItem* sub; ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); QCOMPARE(0,sub->rowCount()); } // command void ProjectControllerTest::fileInSubdirectory() { // verify that the project model contains a single file in a subfolder // First setup a FakeFileManager with this folder + file Project* proj = new Project(); KUrl folderUrl = KUrl(m_projFolder, "foobar/"); FakeFileManager* fileMng = createFileManager(); fileMng->addSubFolderTo(m_projFolder, folderUrl); KUrl fileUrl = KUrl(folderUrl, "zoo"); fileMng->addFileToFolder(folderUrl, fileUrl); proj->setManagerPlugin(fileMng); proj->open(m_projFileUrl); WAIT_FOR_OPEN_SIGNAL; ProjectFolderItem* rootFolder; ProjectFolderItem* sub; ProjectFileItem* file; ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",fileUrl,file); proj->reloadModel(); QTest::qWait(100); // NO signals for reload ... ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder); ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderUrl, sub); ASSERT_SINGLE_FILE_IN(sub,"zoo",fileUrl,file); } ////////////////////// Helpers /////////////////////////////////////////////// KUrl ProjectControllerTest::writeProjectConfig(const QString& name) { KUrl configUrl = KUrl(m_scratchDir.absolutePath() + '/' + name + ".kdev4"); QFile f(configUrl.pathOrUrl()); f.open(QIODevice::WriteOnly); QTextStream str(&f); str << "[Project]\n" << "Name=" << name << "\n"; f.close(); return configUrl; } ////////////////// Custom assertions ///////////////////////////////////////// void ProjectControllerTest::assertProjectOpened(const QString& name, IProject*& proj) { QVERIFY(proj = m_projCtrl->findProjectByName(name)); QVERIFY(m_projCtrl->projects().contains(proj)); } void ProjectControllerTest::assertSpyCaughtProject(QSignalSpy* spy, IProject* proj) { QCOMPARE(spy->size(), 1); IProject* emittedProj = (*spy)[0][0].value(); QCOMPARE(proj, emittedProj); } void ProjectControllerTest::assertProjectClosed(IProject* proj) { IProject* p = m_projCtrl->findProjectByName(proj->name()); QVERIFY(p == 0); QVERIFY(!m_projCtrl->projects().contains(proj)); } void ProjectControllerTest::assertEmptyProjectModel() { ProjectModel* m = m_projCtrl->projectModel(); Q_ASSERT(m); QCOMPARE(m->rowCount(), 0); } ///////////////////// Creation stuff ///////////////////////////////////////// QSignalSpy* ProjectControllerTest::createOpenedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*))); } QSignalSpy* ProjectControllerTest::createClosedSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosed(KDevelop::IProject*))); } QSignalSpy* ProjectControllerTest::createClosingSpy() { return new QSignalSpy(m_projCtrl, SIGNAL(projectClosing(KDevelop::IProject*))); } FakeFileManager* ProjectControllerTest::createFileManager() { FakeFileManagerFactory* f = new FakeFileManagerFactory(); FakeFileManager* fileMng = qobject_cast(f->create()); m_fileManagerGarbage << fileMng; return fileMng; } QTEST_KDEMAIN( ProjectControllerTest, GUI) #include "moc_projectcontrollertest.cpp" #include "projectcontrollertest.moc"