File indexing completed on 2024-05-12 04:38:22

0001 /*
0002     SPDX-FileCopyrightText: 2008 Manuel Breugelmans <mbr.nxi@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "test_projectcontroller.h"
0008 
0009 #include <QFile>
0010 #include <QSignalSpy>
0011 #include <QTest>
0012 
0013 #include <KAboutData>
0014 
0015 #include <tests/testhelpers.h>
0016 #include <tests/autotestshell.h>
0017 #include <tests/testcore.h>
0018 
0019 #include <interfaces/iplugin.h>
0020 #include <project/interfaces/iprojectfilemanager.h>
0021 #include <project/projectmodel.h>
0022 #include <shell/core.h>
0023 #include <shell/projectcontroller.h>
0024 #include <shell/plugincontroller.h>
0025 #include <shell/project.h>
0026 
0027 using namespace KDevelop;
0028 
0029 namespace {
0030 
0031 class DialogProviderFake : public IProjectDialogProvider
0032 {
0033 Q_OBJECT
0034 public:
0035     DialogProviderFake()
0036     {}
0037     ~DialogProviderFake() override {}
0038     bool m_reopen = true;
0039 
0040 public Q_SLOTS:
0041     QUrl askProjectConfigLocation(bool /*fetch*/, const QUrl& /*startUrl*/,
0042                                   const QUrl& /*repoUrl*/, IPlugin* /*plugin*/) override
0043     { return QUrl(); }
0044     bool userWantsReopen() override { return m_reopen; }
0045 };
0046 
0047 }
0048 
0049 /*! A Filemanager plugin that allows you to setup a file & directory structure */
0050 class FakeFileManager : public IPlugin, public IProjectFileManager
0051 {
0052     Q_OBJECT
0053     Q_INTERFACES(KDevelop::IProjectFileManager)
0054 public:
0055     FakeFileManager(QObject*, const QVariantList&)
0056         : IPlugin(QStringLiteral("FakeFileManager"), Core::self())
0057     {
0058     }
0059 
0060     FakeFileManager()
0061         : IPlugin(QStringLiteral("FakeFileManager"), Core::self())
0062     {
0063     }
0064 
0065     ~FakeFileManager() override {}
0066 
0067     Features features() const override
0068     {
0069         return IProjectFileManager::Files | IProjectFileManager::Folders;
0070     }
0071 
0072     QMap<Path, Path::List> m_filesInFolder; // initialize
0073     QMap<Path, Path::List> m_subFoldersInFolder;
0074 
0075     /*! Setup this manager such that @p folder contains @p file */
0076     void addFileToFolder(const Path& folder, const Path& file)
0077     {
0078         if (!m_filesInFolder.contains(folder)) {
0079             m_filesInFolder[folder] = Path::List();
0080         }
0081         m_filesInFolder[folder] << file;
0082     }
0083 
0084     /*! Setup this manager such that @p folder has @p subFolder */
0085     void addSubFolderTo(const Path& folder, const Path& subFolder)
0086     {
0087         if (!m_subFoldersInFolder.contains(folder)) {
0088             m_subFoldersInFolder[folder] = Path::List();
0089         }
0090         m_subFoldersInFolder[folder] << subFolder;
0091     }
0092 
0093     QList<ProjectFolderItem*> parse(ProjectFolderItem *dom) override
0094     {
0095         const Path::List files = m_filesInFolder[dom->path()];
0096         for (const Path& file : files) {
0097             new ProjectFileItem(dom->project(), file, dom);
0098         }
0099         const Path::List folderPaths = m_subFoldersInFolder[dom->path()];
0100         QList<ProjectFolderItem*> folders;
0101         for (const Path& folderPath : folderPaths) {
0102             folders << new ProjectFolderItem(dom->project(), folderPath, dom);
0103         }
0104         return folders;
0105     }
0106 
0107     ProjectFolderItem *import(IProject *project) override
0108     {
0109         auto* it = new ProjectFolderItem(project, project->path());
0110         return it;
0111     }
0112 
0113     ProjectFolderItem* addFolder(const Path& /*folder*/, ProjectFolderItem* /*parent*/) override { return nullptr; }
0114     ProjectFileItem* addFile(const Path& /*file*/, ProjectFolderItem* /*parent*/) override { return nullptr; }
0115     bool removeFilesAndFolders(const QList<ProjectBaseItem*> &/*items*/) override { return false; }
0116     bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; }
0117     bool copyFilesAndFolders(const Path::List &/*items*/, KDevelop::ProjectFolderItem* /*newParent*/) override { return false; }
0118     bool renameFile(ProjectFileItem* /*file*/, const Path& /*newPath*/) override { return false; }
0119     bool renameFolder(ProjectFolderItem* /*oldFolder*/, const Path& /*newPath*/ ) override { return false; }
0120     bool reload(ProjectFolderItem* /*item*/) override { return false; }
0121 };
0122 
0123 class FakePluginController : public PluginController
0124 {
0125     Q_OBJECT
0126 public:
0127     using PluginController::PluginController;
0128 
0129     IPlugin* pluginForExtension(const QString& extension, const QString& pluginName = {}, const QVariantMap& constraints = QVariantMap()) override
0130     {
0131         if (extension == qobject_interface_iid<IProjectFileManager*>()) {
0132             if (!m_fakeFileManager) {
0133                 // Can't initialize in the constructor, because the pluginController must be setup
0134                 //  before constructing a plugin, and this _is_ the pluginController.
0135                 m_fakeFileManager = new FakeFileManager;
0136             }
0137             return m_fakeFileManager;
0138         }
0139         return PluginController::pluginForExtension(extension, pluginName, constraints);
0140     }
0141 
0142 private:
0143     FakeFileManager* m_fakeFileManager = nullptr;
0144 };
0145 
0146 ////////////////////// Fixture ///////////////////////////////////////////////
0147 
0148 void TestProjectController::initTestCase()
0149 {
0150     AutoTestShell::init({{}});
0151     auto* testCore = new TestCore;
0152     testCore->setPluginController( new FakePluginController(testCore) );
0153     testCore->initialize();
0154     qRegisterMetaType<KDevelop::IProject*>();
0155     m_core = Core::self();
0156     m_scratchDir = QDir(QDir::tempPath());
0157     m_scratchDir.mkdir(QStringLiteral("prjctrltest"));
0158     m_scratchDir.cd(QStringLiteral("prjctrltest"));
0159 
0160     QSignalSpy projectControllerInitializedSpy(m_core->projectControllerInternal(),
0161                                                &ProjectController::initialized);
0162     QVERIFY(projectControllerInitializedSpy.wait(100));
0163 }
0164 
0165 void TestProjectController::cleanupTestCase()
0166 {
0167     TestCore::shutdown();
0168 }
0169 
0170 void TestProjectController::init()
0171 {
0172     m_projName = QStringLiteral("foo");
0173     m_projFilePath = writeProjectConfig(m_projName);
0174     m_projCtrl = m_core->projectControllerInternal();
0175     m_tmpConfigs << m_projFilePath;
0176     m_projFolder = Path(m_scratchDir.absolutePath() + '/');
0177 }
0178 
0179 void TestProjectController::cleanup()
0180 {
0181     // also close any opened projects as we do not get a clean fixture,
0182     // following tests should start off clean.
0183     const auto projects = m_projCtrl->projects();
0184     for (IProject* p : projects) {
0185         m_projCtrl->closeProject(p);
0186     }
0187     for (const Path& cfg : qAsConst(m_tmpConfigs)) {
0188         QFile::remove(cfg.pathOrUrl());
0189     }
0190     qDeleteAll(m_fileManagerGarbage);
0191     m_fileManagerGarbage.clear();
0192 }
0193 
0194 ////////////////////// Commands //////////////////////////////////////////////
0195 
0196 #define WAIT_FOR_OPEN_SIGNAL \
0197 {\
0198     QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)));\
0199     QVERIFY2(signal.wait(30000), "Timeout while waiting for opened signal");\
0200 } void(0)
0201 
0202 void TestProjectController::openProject()
0203 {
0204     auto spy = createOpenedSpy();
0205     QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName));
0206     m_projCtrl->openProject(m_projFilePath.toUrl());
0207     WAIT_FOR_OPEN_SIGNAL;
0208     QCOMPARE(m_projCtrl->projectCount(), 1);
0209     auto* proj = assertProjectOpened(m_projName);
0210     assertSpyCaughtProject(spy.get(), proj);
0211     QCOMPARE(proj->projectFile(), m_projFilePath);
0212     QCOMPARE(proj->path(), Path(m_scratchDir.absolutePath()+'/'));
0213     QVERIFY(m_projCtrl->isProjectNameUsed(m_projName));
0214 }
0215 
0216 void TestProjectController::closeProject()
0217 {
0218     m_projCtrl->openProject(m_projFilePath.toUrl());
0219     WAIT_FOR_OPEN_SIGNAL;
0220     IProject* proj = m_projCtrl->findProjectByName(m_projName);
0221     Q_ASSERT(proj);
0222 
0223     auto spy1 = createClosedSpy();
0224     auto spy2 = createClosingSpy();
0225     m_projCtrl->closeProject(proj);
0226 
0227     QVERIFY(!m_projCtrl->isProjectNameUsed(m_projName));
0228     QCOMPARE(m_projCtrl->projectCount(), 0);
0229     assertProjectClosed(proj);
0230     assertSpyCaughtProject(spy1.get(), proj);
0231     assertSpyCaughtProject(spy2.get(), proj);
0232 }
0233 
0234 void TestProjectController::openCloseOpen()
0235 {
0236     m_projCtrl->openProject(m_projFilePath.toUrl());
0237     WAIT_FOR_OPEN_SIGNAL;
0238     auto* proj = assertProjectOpened(m_projName);
0239     m_projCtrl->closeProject(proj);
0240     auto spy = createOpenedSpy();
0241     m_projCtrl->openProject(m_projFilePath.toUrl());
0242     WAIT_FOR_OPEN_SIGNAL;
0243     QVERIFY(m_projCtrl->isProjectNameUsed(m_projName));
0244     QCOMPARE(m_projCtrl->projectCount(), 1);
0245     proj = assertProjectOpened(m_projName);
0246     assertSpyCaughtProject(spy.get(), proj);
0247 }
0248 
0249 void TestProjectController::reopen()
0250 {
0251     m_projCtrl->setDialogProvider(new DialogProviderFake);
0252     m_projCtrl->openProject(m_projFilePath.toUrl());
0253     WAIT_FOR_OPEN_SIGNAL;
0254     auto spy = createOpenedSpy();
0255     m_projCtrl->openProject(m_projFilePath.toUrl());
0256     WAIT_FOR_OPEN_SIGNAL;
0257     QCOMPARE(m_projCtrl->projectCount(), 1);
0258     QVERIFY(m_projCtrl->isProjectNameUsed(m_projName));
0259     auto* proj = assertProjectOpened(m_projName);
0260     assertSpyCaughtProject(spy.get(), proj);
0261 }
0262 
0263 void TestProjectController::reopenWhileLoading()
0264 {
0265     // Open the same project again while the first is still
0266     // loading. The second open request should be blocked.
0267     m_projCtrl->setDialogProvider(new DialogProviderFake);
0268     auto spy = createOpenedSpy();
0269     m_projCtrl->openProject(m_projFilePath.toUrl());
0270     //m_projCtrl->openProject(m_projFilePath.toUrl());
0271     WAIT_FOR_OPEN_SIGNAL;
0272     // wait a bit for a second signal, this should timeout
0273     QSignalSpy signal(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)));
0274     QVERIFY2(!signal.wait(100), "Received 2 projectOpened signals.");
0275     QCOMPARE(m_projCtrl->projectCount(), 1);
0276     auto* proj = assertProjectOpened(m_projName);
0277     assertSpyCaughtProject(spy.get(), proj);
0278 }
0279 
0280 void TestProjectController::openMultiple()
0281 {
0282     QString secondProj(QStringLiteral("bar"));
0283     Path secondCfgUrl = writeProjectConfig(secondProj);
0284     auto spy = createOpenedSpy();
0285     m_projCtrl->openProject(m_projFilePath.toUrl());
0286     WAIT_FOR_OPEN_SIGNAL;
0287     m_projCtrl->openProject(secondCfgUrl.toUrl());
0288     WAIT_FOR_OPEN_SIGNAL;
0289 
0290     QCOMPARE(m_projCtrl->projectCount(), 2);
0291     auto* proj1 = assertProjectOpened(m_projName);
0292     auto* proj2 = assertProjectOpened(secondProj);
0293 
0294     QVERIFY(m_projCtrl->isProjectNameUsed(m_projName));
0295     QVERIFY(m_projCtrl->isProjectNameUsed(QStringLiteral("bar")));
0296 
0297     QCOMPARE(spy->size(), 2);
0298     auto* emittedProj1 = (*spy)[0][0].value<IProject*>();
0299     auto* emittedProj2 = (*spy)[1][0].value<IProject*>();
0300     QCOMPARE(emittedProj1, proj1);
0301     QCOMPARE(emittedProj2, proj2);
0302 
0303     m_tmpConfigs << secondCfgUrl;
0304 }
0305 
0306 /*! Verify that the projectmodel contains a single project. Put this project's
0307  *  ProjectFolderItem in the output parameter @p RootItem */
0308 #define ASSERT_SINGLE_PROJECT_IN_MODEL(rootItem) \
0309 {\
0310     QCOMPARE(m_projCtrl->projectModel()->rowCount(), 1); \
0311     QModelIndex projIndex = m_projCtrl->projectModel()->index(0,0); \
0312     QVERIFY(projIndex.isValid()); \
0313     ProjectBaseItem* i = m_projCtrl->projectModel()->itemFromIndex( projIndex ); \
0314     QVERIFY(i); \
0315     QVERIFY(i->folder()); \
0316     rootItem = i->folder();\
0317 } void(0)
0318 
0319 /*! Verify that the projectitem @p item has a single child item
0320  *  named @p name with url @p url. @p subFolder is an output parameter
0321  *  that contains the sub-folder projectitem. */
0322 #define ASSERT_SINGLE_SUBFOLDER_IN(item, name, path__, subFolder) \
0323 {\
0324     QCOMPARE(item->rowCount(), 1);\
0325     QCOMPARE(item->folderList().size(), 1);\
0326     ProjectFolderItem* fo = item->folderList().at(0);\
0327     QVERIFY(fo);\
0328     QCOMPARE(fo->path(), path__);\
0329     QCOMPARE(fo->folderName(), QStringLiteral(name));\
0330     subFolder = fo;\
0331 } void(0)
0332 
0333 #define ASSERT_SINGLE_FILE_IN(rootFolder, name, path__, fileItem)\
0334 {\
0335     QCOMPARE(rootFolder->rowCount(), 1);\
0336     QCOMPARE(rootFolder->fileList().size(), 1);\
0337     fileItem = rootFolder->fileList().at(0);\
0338     QVERIFY(fileItem);\
0339     QCOMPARE(fileItem->path(), path__);\
0340     QCOMPARE(fileItem->fileName(), QStringLiteral(name));\
0341 } void(0)
0342 
0343 // command
0344 void TestProjectController::emptyProject()
0345 {
0346     // verify that the project model contains a single top-level folder after loading
0347     // an empty project
0348 
0349     assertEmptyProjectModel();
0350 
0351     m_projCtrl->openProject(m_projFilePath.toUrl());
0352     WAIT_FOR_OPEN_SIGNAL;
0353     auto* proj = assertProjectOpened(m_projName);
0354 
0355     FakeFileManager* fileMng = createFileManager();
0356     Q_ASSERT(fileMng);
0357 
0358     proj->setManagerPlugin(fileMng);
0359     proj->reloadModel();
0360     QTest::qWait(100);
0361 
0362     ProjectFolderItem* rootFolder;
0363     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0364 
0365     // check that the project is empty
0366     QCOMPARE(rootFolder->rowCount(), 0);
0367     QCOMPARE(rootFolder->project()->name(), m_projName);
0368     QCOMPARE(rootFolder->path(), m_projFolder);
0369 }
0370 
0371 // command
0372 void TestProjectController::singleFile()
0373 {
0374     // verify that the project model contains a single file in the
0375     // top folder. First setup a FakeFileManager with this file
0376 
0377     m_projCtrl->openProject(m_projFilePath.toUrl());
0378     WAIT_FOR_OPEN_SIGNAL;
0379     auto* proj = assertProjectOpened(m_projName);
0380 
0381     FakeFileManager* fileMng = createFileManager();
0382     proj->setManagerPlugin(fileMng);
0383 
0384     Path filePath = Path(m_projFolder, QStringLiteral("foobar"));
0385     fileMng->addFileToFolder(m_projFolder, filePath);
0386 
0387     proj->reloadModel();
0388     QTest::qWait(100); // NO signals for reload ...
0389 
0390     ProjectFolderItem* rootFolder;
0391     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0392     ProjectFileItem* fi;
0393     ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi);
0394     QCOMPARE(fi->rowCount(), 0);
0395 
0396     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0397     ASSERT_SINGLE_FILE_IN(rootFolder, "foobar", filePath, fi);
0398 }
0399 
0400 // command
0401 void TestProjectController::singleDirectory()
0402 {
0403     // verify that the project model contains a single folder in the
0404     // top folder. First setup a FakeFileManager with this folder
0405 
0406     m_projCtrl->openProject(m_projFilePath.toUrl());
0407     WAIT_FOR_OPEN_SIGNAL;
0408     auto* proj = assertProjectOpened(m_projName);
0409 
0410     Path folderPath = Path(m_projFolder, QStringLiteral("foobar/"));
0411     FakeFileManager* fileMng = createFileManager();
0412     fileMng->addSubFolderTo(m_projFolder, folderPath);
0413 
0414     proj->setManagerPlugin(fileMng);
0415     proj->reloadModel();
0416     QTest::qWait(100);
0417 
0418     ProjectFolderItem* rootFolder;
0419     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0420 
0421     // check that the project contains a single subfolder
0422     ProjectFolderItem* sub;
0423     ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub);
0424     QCOMPARE(sub->rowCount(), 0);
0425 }
0426 
0427 // command
0428 void TestProjectController::fileInSubdirectory()
0429 {
0430     // verify that the project model contains a single file in a subfolder
0431     // First setup a FakeFileManager with this folder + file
0432 
0433     m_projCtrl->openProject(m_projFilePath.toUrl());
0434     WAIT_FOR_OPEN_SIGNAL;
0435     auto* proj = assertProjectOpened(m_projName);
0436 
0437     Path folderPath = Path(m_projFolder, QStringLiteral("foobar/"));
0438     FakeFileManager* fileMng = createFileManager();
0439     fileMng->addSubFolderTo(m_projFolder, folderPath);
0440     Path filePath = Path(folderPath, QStringLiteral("zoo"));
0441     fileMng->addFileToFolder(folderPath, filePath);
0442 
0443     proj->setManagerPlugin(fileMng);
0444     ProjectFolderItem* rootFolder = nullptr;
0445     ProjectFolderItem* sub = nullptr;
0446     ProjectFileItem* file = nullptr;
0447 
0448     proj->reloadModel();
0449     QTest::qWait(100);
0450 
0451     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0452     ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub);
0453     ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file);
0454 
0455     ASSERT_SINGLE_PROJECT_IN_MODEL(rootFolder);
0456     ASSERT_SINGLE_SUBFOLDER_IN(rootFolder, "foobar", folderPath, sub);
0457     ASSERT_SINGLE_FILE_IN(sub,"zoo",filePath,file);
0458 }
0459 
0460 void TestProjectController::prettyFileName_data()
0461 {
0462     QTest::addColumn<QString>("relativeFilePath");
0463 
0464     QTest::newRow("basic")
0465         << "foobar.txt";
0466     QTest::newRow("subfolder")
0467         << "sub/foobar.txt";
0468 }
0469 
0470 void TestProjectController::prettyFileName()
0471 {
0472     QFETCH(QString, relativeFilePath);
0473 
0474     m_projCtrl->openProject(m_projFilePath.toUrl());
0475     WAIT_FOR_OPEN_SIGNAL;
0476     auto* proj = assertProjectOpened(m_projName);
0477 
0478     FakeFileManager* fileMng = createFileManager();
0479     proj->setManagerPlugin(fileMng);
0480 
0481     Path filePath = Path(m_projFolder, relativeFilePath);
0482     fileMng->addFileToFolder(m_projFolder, filePath);
0483 
0484     QCOMPARE(m_projCtrl->prettyFileName(filePath.toUrl(), ProjectController::FormattingOptions::FormatPlain), QString(m_projName + ':' + relativeFilePath));
0485 }
0486 
0487 ////////////////////// Helpers ///////////////////////////////////////////////
0488 
0489 Path TestProjectController::writeProjectConfig(const QString& name)
0490 {
0491     Path configPath = Path(m_scratchDir.absolutePath() + '/' + name + ".kdev4");
0492     QFile f(configPath.pathOrUrl());
0493     f.open(QIODevice::WriteOnly);
0494     QTextStream str(&f);
0495     str << "[Project]\n"
0496         << "Name=" << name << "\n";
0497     f.close();
0498     return configPath;
0499 }
0500 
0501 ////////////////// Custom assertions /////////////////////////////////////////
0502 
0503 KDevelop::Project* TestProjectController::assertProjectOpened(const QString& name)
0504 {
0505     auto* projRaw = m_projCtrl->findProjectByName(name);
0506     QVERIFY_RETURN(projRaw, nullptr);
0507     QVERIFY_RETURN(m_projCtrl->projects().contains(projRaw), nullptr);
0508 
0509     auto proj = dynamic_cast<KDevelop::Project*>(projRaw);
0510     QVERIFY_RETURN(projRaw, nullptr);
0511     return proj;
0512 }
0513 
0514 void TestProjectController::assertSpyCaughtProject(QSignalSpy* spy, IProject* proj)
0515 {
0516     QCOMPARE(spy->size(), 1);
0517     auto* emittedProj = (*spy)[0][0].value<IProject*>();
0518     QCOMPARE(proj, emittedProj);
0519 }
0520 
0521 void TestProjectController::assertProjectClosed(IProject* proj)
0522 {
0523     IProject* p = m_projCtrl->findProjectByName(proj->name());
0524     QVERIFY(p == nullptr);
0525     QVERIFY(!m_projCtrl->projects().contains(proj));
0526 }
0527 
0528 void TestProjectController::assertEmptyProjectModel()
0529 {
0530     ProjectModel* m = m_projCtrl->projectModel();
0531     Q_ASSERT(m);
0532     QCOMPARE(m->rowCount(), 0);
0533 }
0534 
0535 ///////////////////// Creation stuff /////////////////////////////////////////
0536 
0537 std::unique_ptr<QSignalSpy> TestProjectController::createOpenedSpy()
0538 {
0539     return std::make_unique<QSignalSpy>(m_projCtrl, SIGNAL(projectOpened(KDevelop::IProject*)));
0540 }
0541 
0542 std::unique_ptr<QSignalSpy> TestProjectController::createClosedSpy()
0543 {
0544     return std::make_unique<QSignalSpy>(m_projCtrl, SIGNAL(projectClosed(KDevelop::IProject*)));
0545 }
0546 
0547 std::unique_ptr<QSignalSpy> TestProjectController::createClosingSpy()
0548 {
0549     return std::make_unique<QSignalSpy>(m_projCtrl, SIGNAL(projectClosing(KDevelop::IProject*)));
0550 }
0551 
0552 FakeFileManager* TestProjectController::createFileManager()
0553 {
0554     auto* fileMng = new FakeFileManager;
0555     m_fileManagerGarbage << fileMng;
0556     return fileMng;
0557 }
0558 
0559 QTEST_MAIN(TestProjectController)
0560 #include "moc_test_projectcontroller.cpp"
0561 #include "test_projectcontroller.moc"