1 /*
2     SPDX-FileCopyrightText: 2010 Esben Mose Hansen <kde@mosehansen.dk>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "test_cmakemanager.h"
8 #include "testhelpers.h"
9 #include "cmakemodelitems.h"
10 #include "cmakeutils.h"
11 #include "cmakeimportjsonjob.h"
12 
13 #include <QLoggingCategory>
14 #include <QTemporaryFile>
15 
16 #include <interfaces/icore.h>
17 #include <interfaces/itestsuite.h>
18 #include <interfaces/itestcontroller.h>
19 #include <project/interfaces/ibuildsystemmanager.h>
20 #include <project/abstractfilemanagerplugin.h>
21 #include <tests/autotestshell.h>
22 #include <tests/testproject.h>
23 #include <tests/testcore.h>
24 #include <testing/ctestsuite.h>
25 
26 QTEST_MAIN(TestCMakeManager)
27 
28 using namespace KDevelop;
29 
initTestCase()30 void TestCMakeManager::initTestCase()
31 {
32     QLoggingCategory::setFilterRules("*.debug=false\nkdevplatform.outputview.debug=true\n"
33                                      "kdevelop.plugins.cmake.debug=true\ndefault.debug=true\n");
34 
35     AutoTestShell::init({"KDevCMakeManager", "KDevCMakeBuilder", "KDevMakeBuilder", "KDevStandardOutputView"});
36     TestCore::initialize();
37 
38     cleanup();
39 }
40 
cleanupTestCase()41 void TestCMakeManager::cleanupTestCase()
42 {
43     TestCore::shutdown();
44 }
45 
cleanup()46 void TestCMakeManager::cleanup()
47 {
48     const auto projects = ICore::self()->projectController()->projects();
49     for (IProject* p : projects) {
50         ICore::self()->projectController()->closeProject(p);
51     }
52     QVERIFY(ICore::self()->projectController()->projects().isEmpty());
53 }
54 
testWithBuildDirProject()55 void TestCMakeManager::testWithBuildDirProject()
56 {
57     loadProject(QStringLiteral("with_build_dir"));
58 }
59 
testIncludePaths()60 void TestCMakeManager::testIncludePaths()
61 {
62     IProject* project = loadProject(QStringLiteral("single_subdirectory"));
63     Path sourceDir = project->path();
64 
65     Path fooCpp(sourceDir, QStringLiteral("subdir/foo.cpp"));
66     QVERIFY(QFile::exists(fooCpp.toLocalFile()));
67     QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(fooCpp.pathOrUrl()));
68     ProjectBaseItem* fooCppItem = items.first();
69 
70     Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem);
71     QVERIFY(includeDirs.size() >= 3);
72 
73     Path buildDir(project->buildSystemManager()->buildDirectory(fooCppItem));
74     QVERIFY(includeDirs.contains(buildDir));
75 
76     Path subBuildDir(buildDir, QStringLiteral("subdir/"));
77     QVERIFY(includeDirs.contains(subBuildDir));
78 
79     Path subDir(sourceDir, QStringLiteral("subdir/"));
80     QVERIFY(includeDirs.contains(subDir));
81 }
82 
testRelativePaths()83 void TestCMakeManager::testRelativePaths()
84 {
85     IProject* project = loadProject(QStringLiteral("relative_paths"), QStringLiteral("/out"));
86 
87     Path codeCpp(project->path(), QStringLiteral("../src/code.cpp"));
88     QVERIFY(QFile::exists( codeCpp.toLocalFile()));
89     QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(codeCpp.pathOrUrl()));
90     QCOMPARE(items.size(), 1); // once in the target
91     ProjectBaseItem* fooCppItem = items.first();
92 
93     Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem);
94 
95     Path incDir(project->path(), QStringLiteral("../inc/"));
96     QVERIFY(includeDirs.contains( incDir ));
97 }
98 
testTargetIncludePaths()99 void TestCMakeManager::testTargetIncludePaths()
100 {
101     IProject* project = loadProject(QStringLiteral("target_includes"));
102 
103     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
104     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
105     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
106     QCOMPARE(items.size(), 2); // once the plain file, once the target
107 
108     bool foundInTarget = false;
109     for (ProjectBaseItem* mainCppItem : items) {
110         ProjectBaseItem* mainContainer = mainCppItem->parent();
111 
112         Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
113 
114         if (mainContainer->target()) {
115             foundInTarget = true;
116             Path targetIncludesDir(project->path(), QStringLiteral("includes/"));
117             QVERIFY(includeDirs.contains(targetIncludesDir));
118         }
119     }
120     QVERIFY(foundInTarget);
121 }
122 
testTargetIncludeDirectories()123 void TestCMakeManager::testTargetIncludeDirectories()
124 {
125     IProject* project = loadProject(QStringLiteral("target_include_directories"));
126 
127     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
128     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
129     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
130     QCOMPARE(items.size(), 2); // once the plain file, once the target
131 
132     bool foundInTarget = false;
133     for (ProjectBaseItem* mainCppItem : items) {
134         ProjectBaseItem* mainContainer = mainCppItem->parent();
135 
136         Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
137 
138         if (mainContainer->target()) {
139             foundInTarget = true;
140             QVERIFY(includeDirs.contains(Path(project->path(), "includes/")));
141             QVERIFY(includeDirs.contains(Path(project->path(), "libincludes/")));
142         }
143     }
144     QVERIFY(foundInTarget);
145 }
146 
testQt5App()147 void TestCMakeManager::testQt5App()
148 {
149     IProject* project = loadProject(QStringLiteral("qt5_app"));
150 
151     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
152     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
153     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
154     QCOMPARE(items.size(), 2); // once the plain file, once the target
155 
156     bool foundCore = false, foundGui = false, foundWidgets = false;
157     for (ProjectBaseItem* mainCppItem : items) {
158         const Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
159         for (const Path& include : includeDirs) {
160             QString filename = include.lastPathSegment();
161             foundCore |= filename == QLatin1String("QtCore");
162             foundGui |= filename == QLatin1String("QtGui");
163             foundWidgets |= filename == QLatin1String("QtWidgets");
164         }
165     }
166     QVERIFY(foundCore);
167     QVERIFY(foundGui);
168     QVERIFY(foundWidgets);
169 }
170 
testQt5AppOld()171 void TestCMakeManager::testQt5AppOld()
172 {
173     IProject* project = loadProject(QStringLiteral("qt5_app_old"));
174 
175     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
176     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
177     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
178     QCOMPARE(items.size(), 2); // once the plain file, once the target
179 
180     bool foundCore = false, foundGui = false, foundWidgets = false;
181     for (ProjectBaseItem* mainCppItem : items) {
182         const Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
183         for (const Path& include : includeDirs) {
184             QString filename = include.lastPathSegment();
185             foundCore |= filename == QLatin1String("QtCore");
186             foundGui |= filename == QLatin1String("QtGui");
187             foundWidgets |= filename == QLatin1String("QtWidgets");
188         }
189     }
190     QVERIFY(foundCore);
191     QVERIFY(foundGui);
192     QVERIFY(foundWidgets);
193 }
194 
testKF5App()195 void TestCMakeManager::testKF5App()
196 {
197     IProject* project = loadProject(QStringLiteral("kf5_app"));
198 
199     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
200     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
201     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
202     QCOMPARE(items.size(), 2); // once the plain file, once the target
203 
204     bool foundCore = false, foundGui = false, foundWidgets = false, foundWidgetsAddons = false;
205     for (ProjectBaseItem* mainCppItem : items) {
206         const Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem);
207         qDebug() << "xxxxxxxxx" << includeDirs;
208         for (const Path& include : includeDirs) {
209             QString filename = include.lastPathSegment();
210             foundCore |= filename == QLatin1String("QtCore");
211             foundGui |= filename == QLatin1String("QtGui");
212             foundWidgets |= filename == QLatin1String("QtWidgets");
213             foundWidgetsAddons |= filename == QLatin1String("KWidgetsAddons");
214         }
215     }
216     QVERIFY(foundCore);
217     QVERIFY(foundGui);
218     QVERIFY(foundWidgets);
219     QVERIFY(foundWidgetsAddons);
220 }
221 
testDefines()222 void TestCMakeManager::testDefines()
223 {
224     IProject* project = loadProject(QStringLiteral("defines"));
225 
226     Path mainCpp(project->path(), QStringLiteral("main.cpp"));
227     QVERIFY(QFile::exists(mainCpp.toLocalFile()));
228     const QList<ProjectBaseItem*> items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl()));
229     QCOMPARE(items.size(), 2); // once the plain file, once the target
230 
231     bool foundInTarget = false;
232     for (ProjectBaseItem* mainCppItem : items) {
233         QHash<QString, QString> defines = project->buildSystemManager()->defines(mainCppItem);
234 
235         QCOMPARE(defines.value("B", QStringLiteral("not found")), QString());
236         QCOMPARE(defines.value("BV", QStringLiteral("not found")), QStringLiteral("1"));
237         QCOMPARE(defines.value("BV2", QStringLiteral("not found")), QStringLiteral("2"));
238 
239 //         QCOMPARE(defines.value("BAR", QStringLiteral("not found")), QStringLiteral("foo"));
240 //         QCOMPARE(defines.value("FOO", QStringLiteral("not found")), QStringLiteral("bar"));
241 //         QCOMPARE(defines.value("BLA", QStringLiteral("not found")), QStringLiteral("blub"));
242         QCOMPARE(defines.value("ASDF", QStringLiteral("not found")), QStringLiteral("asdf"));
243         QCOMPARE(defines.value("XYZ", QStringLiteral("not found")), QString());
244         QCOMPARE(defines.value("A", QStringLiteral("not found")), QString());
245         QCOMPARE(defines.value("AV", QStringLiteral("not found")), QStringLiteral("1"));
246         QCOMPARE(defines.value("AV2", QStringLiteral("not found")), QStringLiteral("2"));
247         QCOMPARE(defines.value("C", QStringLiteral("not found")), QString());
248         QCOMPARE(defines.value("CV", QStringLiteral("not found")), QStringLiteral("1"));
249         QCOMPARE(defines.value("CV2", QStringLiteral("not found")), QStringLiteral("2"));
250         QCOMPARE(defines.size(), 13);
251         foundInTarget = true;
252     }
253     QVERIFY(foundInTarget);
254 }
255 
testCustomTargetSources()256 void TestCMakeManager::testCustomTargetSources()
257 {
258     IProject* project = loadProject(QStringLiteral("custom_target_sources"));
259 
260     QList<ProjectTargetItem*> targets = project->buildSystemManager()->targets(project->projectItem());
261     QVERIFY(targets.size() == 1);
262 
263     ProjectTargetItem *target = targets.first();
264     QCOMPARE(target->fileList().size(), 1);
265     QCOMPARE(target->fileList().first()->baseName(), QStringLiteral("foo.cpp"));
266 }
267 
testConditionsInSubdirectoryBasedOnRootVariables()268 void TestCMakeManager::testConditionsInSubdirectoryBasedOnRootVariables()
269 {
270     IProject* project = loadProject(QStringLiteral("conditions_in_subdirectory_based_on_root_variables"));
271 
272     Path rootFooCpp(project->path(), QStringLiteral("foo.cpp"));
273     QVERIFY(QFile::exists(rootFooCpp.toLocalFile()));
274     QList< ProjectBaseItem* > rootFooItems = project->itemsForPath(IndexedString(rootFooCpp.pathOrUrl()));
275     QCOMPARE(rootFooItems.size(), 4); // three items for the targets, one item for the plain file
276 
277     Path subdirectoryFooCpp(project->path(), QStringLiteral("subdirectory/foo.cpp"));
278     QVERIFY(QFile::exists(subdirectoryFooCpp.toLocalFile()));
279     QList< ProjectBaseItem* > subdirectoryFooItems = project->itemsForPath(IndexedString(subdirectoryFooCpp.pathOrUrl()));
280 
281     QCOMPARE(subdirectoryFooItems.size(), 4); // three items for the targets, one item for the plain file
282 }
283 
testEnumerateTargets()284 void TestCMakeManager::testEnumerateTargets()
285 {
286     QString tempDir = QDir::tempPath();
287 
288     QTemporaryFile targetDirectoriesFile;
289     QTemporaryDir subdir;
290 
291     auto opened = targetDirectoriesFile.open();
292     QVERIFY(opened);
293     QVERIFY(subdir.isValid());
294 
295     const QString targetDirectoriesContent = tempDir + "/CMakeFiles/first_target.dir\n" +
296                                              tempDir + "/CMakeFiles/second_target.dir\r\n" +
297                                              tempDir + "/" + subdir.path() + "/CMakeFiles/third_target.dir";
298 
299     targetDirectoriesFile.write(targetDirectoriesContent.toLatin1());
300     targetDirectoriesFile.close();
301 
302     QHash<KDevelop::Path, QStringList> targets =
303         CMake::enumerateTargets(Path(targetDirectoriesFile.fileName()),
304             tempDir, Path(tempDir));
305 
306     QCOMPARE(targets.value(Path(tempDir)).value(0), QStringLiteral("first_target"));
307     QCOMPARE(targets.value(Path(tempDir)).value(1), QStringLiteral("second_target"));
308     QCOMPARE(targets.value(Path(tempDir + "/" + subdir.path())).value(0), QStringLiteral("third_target"));
309 }
310 
testReload()311 void TestCMakeManager::testReload()
312 {
313     IProject* project = loadProject(QStringLiteral("tiny_project"));
314     const Path sourceDir = project->path();
315 
316     auto fmp = dynamic_cast<AbstractFileManagerPlugin*>(project->projectFileManager());
317     QVERIFY(fmp);
318 
319     auto projectItem = project->projectItem();
320 
321     auto targets = projectItem->targetList();
322     QCOMPARE(targets.size(), 1);
323     auto target = dynamic_cast<CMakeTargetItem*>(targets.first());
324     QVERIFY(target);
325     QCOMPARE(target->text(), QStringLiteral("foo"));
326 
327     auto job = fmp->createImportJob(project->projectItem());
328     project->setReloadJob(job);
329 
330     QSignalSpy spy(job, &KJob::finished);
331     job->start();
332     QVERIFY(spy.wait());
333     QCOMPARE(spy.count(), 1);
334 
335     QCOMPARE(projectItem, project->projectItem());
336     targets = projectItem->targetList();
337     QCOMPARE(targets.size(), 1);
338     target = dynamic_cast<CMakeTargetItem*>(targets.first());
339     QVERIFY(target);
340     QCOMPARE(target->text(), QStringLiteral("foo"));
341 }
342 
testFaultyTarget()343 void TestCMakeManager::testFaultyTarget()
344 {
345     loadProject(QStringLiteral("faulty_target"));
346 }
347 
testParenthesesInTestArguments()348 void TestCMakeManager::testParenthesesInTestArguments()
349 {
350     IProject* project = loadProject(QStringLiteral("parentheses_in_test_arguments"));
351 
352     auto job = new CMakeImportJsonJob(project, this);
353     QVERIFY(job->exec());
354 }
355 
testExecutableOutputPath()356 void TestCMakeManager::testExecutableOutputPath()
357 {
358     const auto prevSuitesCount = ICore::self()->testController()->testSuites().count();
359     qRegisterMetaType<KDevelop::ITestSuite*>("KDevelop::ITestSuite*");
360     QSignalSpy spy(ICore::self()->testController(), &ITestController::testSuiteAdded);
361 
362     IProject* project = loadProject(QStringLiteral("randomexe"));
363     const auto targets = project->projectItem()->targetList();
364     QCOMPARE(targets.count(), 1);
365 
366     const auto target = targets.first()->executable();
367     QVERIFY(target);
368     const KDevelop::Path exePath(target->executable()->builtUrl());
369     QCOMPARE(exePath, KDevelop::Path(project->buildSystemManager()->buildDirectory(project->projectItem()), QLatin1String("randomplace/mytest")));
370 
371     QVERIFY(spy.count() || spy.wait(100000));
372 
373     auto suites = ICore::self()->testController()->testSuites();
374     QCOMPARE(suites.count(), prevSuitesCount + 1);
375     const CTestSuite* suite = static_cast<CTestSuite*>(ICore::self()->testController()->findTestSuite(project, "mytest"));
376     QCOMPARE(suite->executable(), exePath);
377 }
378