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"