CMake support: avoid infinite loop on foreach(RANGE) that never runs.
[kdevelop:kdevelop.git] / projectmanagers / cmake / tests / cmake_cmakeprojectvisitor_test.cpp
1 /* KDevelop CMake Support
2  *
3  * Copyright 2008 Aleix Pol Gonzalez <aleixpol@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA.
19  */
20
21 #include "cmake_cmakeprojectvisitor_test.h"
22 #include "cmakeast.h"
23 #include "cmakeprojectvisitor.h"
24 #include "cmakelistsparser.h"
25 #include <QString>
26 #include <qtest_kde.h>
27 #include <language/duchain/indexedstring.h>
28 #include <language/duchain/duchainlock.h>
29 #include <language/duchain/duchain.h>
30 #include <cmakecondition.h>
31 #include <cmakeparserutils.h>
32 #include <tests/autotestshell.h>
33 #include <astfactory.h>
34
35 QTEST_KDEMAIN_CORE(CMakeProjectVisitorTest)
36
37 using namespace KDevelop;
38
39 #undef TRUE //krazy:exclude=captruefalse
40 #undef FALSE //krazy:exclude=captruefalse
41
42 CMakeProjectVisitorTest::CMakeProjectVisitorTest()
43  : CMakeProjectVisitor( QString(), 0)
44 {
45     AutoTestShell::init();
46     KDevelop::Core::initialize(0, KDevelop::Core::NoUi);
47 }
48
49 void CMakeProjectVisitorTest::testVariables_data()
50 {
51     QTest::addColumn<QString>("input");
52     QTest::addColumn<QStringList>("result");
53     
54     QTest::newRow("a variable") << "${MY_VAR}" << QStringList("MY_VAR");
55     QTest::newRow("env var") << "$ENV{MY_VAR}" << QStringList("MY_VAR");
56     QTest::newRow("Contains a variable") << "${MY_VAR}/lol" << QStringList("MY_VAR");
57     QTest::newRow("Contains a variable") << "${yipiee}#include <${it}>\n" << (QStringList("yipiee") << "it");
58     QTest::newRow("Contains a variable") << "${a}${b}\n" << (QStringList("a") << "b");
59     QTest::newRow("mess") << "{}{}{}}}}{{{{}${a}\n" << QStringList("a");
60     QTest::newRow("Nothing") << "aaaa${aaaa" << QStringList();
61     QTest::newRow("varinvar") << "${${${a}}}" << (QStringList() << "${${a}}" << "${a}" << "a");
62     QTest::newRow("varsinvar") << "${${a}${b}a" << (QStringList() << "a" << "b");
63     QTest::newRow("varsinvar") << "${a${b}a}${a${b}a}" << (QStringList() << "a${b}a" << "b" << "a${b}a" << "b");
64 }
65
66 void CMakeProjectVisitorTest::testVariables()
67 {
68     QFETCH(QString, input);
69     QFETCH(QStringList, result);
70     
71     QStringList name;
72     QList<CMakeProjectVisitor::IntPair> variables =CMakeProjectVisitor::parseArgument(input);
73     
74 //     qDebug() << "kakakaka" << result << variables;
75     QCOMPARE(result.count(), variables.count());
76     if(!variables.isEmpty())
77         QCOMPARE(1, variables.last().level);
78     
79     typedef QPair<int,int> IntPair;
80     foreach(const CMakeProjectVisitor::IntPair& v, variables)
81     {
82         QString name=input.mid(v.first+1, v.second-v.first-1);
83         if(!result.contains(name))
84             qDebug() << "not a var:" << name;
85         QVERIFY(result.contains(name));
86     }
87 }
88
89 typedef QPair<QString, QString> StringPair;
90 Q_DECLARE_METATYPE(QList<StringPair>)
91
92 void CMakeProjectVisitorTest::testRun_data()
93 {
94     QTest::addColumn<QString>("input");
95     QTest::addColumn<QList<StringPair> >("cache");
96     QTest::addColumn<QList<StringPair> >("results");
97
98     QList<StringPair> cacheValues;
99     cacheValues << StringPair("aaa", "cmd");
100     cacheValues << StringPair("bbb", "cmd");
101     cacheValues << StringPair("ccc", "cmd");
102     cacheValues << StringPair("ddd", "cmd");
103     cacheValues << StringPair("eee", "cmd");
104     cacheValues << StringPair("fff", "cmd");
105     cacheValues << StringPair("ggg", "cmd");
106
107     QList<StringPair> results;
108     results << StringPair("aaa", "cmd");
109     results << StringPair("bbb", "cmd");
110     results << StringPair("ccc", "cmd");
111     results << StringPair("ddd", "script");
112     results << StringPair("eee", "cmd");
113     results << StringPair("fff", "cmd");
114     results << StringPair("ggg", "cmd");
115     QTest::newRow("cache") <<
116             "project(simpletest)\n"
117             "cmake_minimum_required(VERSION 2.6)\n"
118             "find_file(aaa stdio.h /usr/include)\n"
119             "set(bbb script CACHE STRING HELLO)\n"
120             "set(ccc script CACHE STRING HELLO FORCE)\n"
121             "set(ddd script)\n"
122             "#message(STATUS \"ooooo- ${aaa} ${bbb} ${ccc} ${ddd}\")\n"
123             "find_path(eee stdio.h /usr/include)\n"
124             "find_library(fff stdio.h /usr/include)\n"
125             "find_program(ggg gcc /usr/gcc)\n" << cacheValues << results;
126             
127     cacheValues.clear();
128     results.clear();
129     results << StringPair("FOOBAR", "ORT Basket Is Strange ABORT");
130     results << StringPair("RES", "Ok");
131     results << StringPair("BARFOO", "ORT Is Basket Strange? ABORT");
132     results << StringPair("BARFOO_MATCH", "Basket Is");
133     results << StringPair("BARFOO_MATCHALL", "Basket Is;Basket Is;Basket Is;Basket Is");
134     QTest::newRow("string") << 
135             "set(FOOBAR \"ORT Basket Is Strange ABORT\")\n"
136             "if( FOOBAR MATCHES \"^ORT Bas\")\n"
137             "  set(RES Ok)\n"
138             "else( FOOBAR MATCHES \"^ORT Bas\")\n"
139             "  set(RES Wrong)\n"
140             "endif( FOOBAR MATCHES \"^ORT Bas\")\n"
141             "string( REGEX REPLACE \"Basket ([a-zA-Z]*) ([a-zA-Z]*)\" \"\\\\1 Basket \\\\2?\" BARFOO ${FOOBAR})\n"
142             "string( REGEX MATCH \"Basket Is\" BARFOO_MATCH ${FOOBAR} ${RES} ${FOOBAR})\n"
143             "string( REGEX MATCHALL \"Basket Is\" BARFOO_MATCHALL ${FOOBAR} \"${FOOBAR}${RES}${FOOBAR}\" ${FOOBAR})\n"
144             << cacheValues << results;
145             
146     
147     cacheValues.clear();
148     results.clear();
149     results << StringPair("kkk", "abcdef");
150     QTest::newRow("abc") << "set(a abc)\n"
151                             "set(b def)\n"
152                             "SET(kkk \"${a}${b}\")\n" << cacheValues << results;
153     
154     cacheValues.clear();
155     results.clear();
156     results << StringPair("kkk", "abcdef");
157     QTest::newRow("defabc") << "set(a abc)\nset(b def)\nSET(kkk \"${kkk}${a}\")\nSET(kkk \"${kkk}${b}\")\n" << cacheValues << results;
158     
159     cacheValues.clear();
160     results.clear();
161     results << StringPair("_INCLUDE_FILES", "#include <a>\n"
162                                             "#include <b>\n"
163                                             "#include <c>\n");
164     QTest::newRow("foreach") <<
165             "set(_HEADER a b c)\n"
166             "FOREACH (it ${_HEADER})\n"
167             "    SET(_INCLUDE_FILES \"${_INCLUDE_FILES}#include <${it}>\n\")\n"
168             "ENDFOREACH (it)\n" << cacheValues << results;
169             
170     cacheValues.clear();
171     results.clear();
172     results << StringPair("b", "abc");
173     results << StringPair("c", "def");
174     QTest::newRow("semicolons1") << "set(a abc;def)\n" 
175                                "LIST(GET a 1 c)\nLIST(GET a 0 b)\n" << cacheValues << results;
176    
177     cacheValues.clear();
178     results.clear();
179     results << StringPair("a", "potatoe");
180     results << StringPair("b", "def");
181     QTest::newRow("varinvar") << "set(a potatoe)\n"
182                                     "set(potatoe \"abc\")\n"
183                                     "set(abc \"def\")\n"
184                                     "set(b \"${${${a}}}\")\n)" << cacheValues << results;
185     
186     cacheValues.clear();
187     results.clear();
188     results << StringPair("b", "k");
189     QTest::newRow("envCC") <<   "set(a $ENV{PATH})\n"
190                                 "if(DEFINED a)\n"
191                                 "   set(b k)\n"
192                                 "endif(DEFINED a)\n"<< cacheValues << results;
193     
194     cacheValues.clear();
195     results.clear();
196     results << StringPair("res", "${caca}");
197     QTest::newRow("strange_var") <<   "set(caca aaaa)\n"
198                                 "set(v1 \"{ca\")\n"
199                                 "set(v2 \"ca}\")\n"
200                                 "set(res \"$${v1}${v2}\")\"\n" << cacheValues << results;
201                                 
202     cacheValues.clear();
203     results.clear();
204     results << StringPair("res", "222aaaa333");
205     QTest::newRow("concatenation") <<   "set(tt aaaa)\n"
206                                 "set(res \"222${tt}333\")\"\n" << cacheValues << results;
207     
208     cacheValues.clear();
209     results.clear();
210     results << StringPair("res", "222aaaa333");
211     QTest::newRow("invar_concatenation") << "set(abc aaaa)\n"
212                                             "set(kk b)\n"
213                                             "set(res \"222${a${kk}c}333\")\n" << cacheValues << results;
214     
215                                         
216     cacheValues.clear();
217     results.clear();
218     results << StringPair("res", "oooaaaa");
219     QTest::newRow("composing") <<   "set(tt aaaa)\n"
220                                     "set(a t)\n"
221                                     "set(b t)\n"
222                                     "set(res ooo${${a}${b}})\"\n" << cacheValues << results;
223     cacheValues.clear();
224     results.clear();
225     results << StringPair("res", "aaaaaa");
226     QTest::newRow("composing1") <<  "set(tt aaaaa)\n"
227                                     "set(a t)\n"
228                                     "set(res \"${${a}${a}}a\")\n" << cacheValues << results;
229                                     
230     cacheValues.clear();
231     results.clear();
232     results << StringPair("_deps", "");
233     QTest::newRow("mad") << "set(_deps tamare)\n"
234                             "aux_source_directory(/tmp _deps)\n"//any unimplemented method
235                             "set(_deps ${_deps})\n"
236                             "set(${${a}${b}a 33)\n" << cacheValues << results;
237                             
238     cacheValues.clear();
239     results.clear();
240     results << StringPair("res", "1");
241     QTest::newRow("parenthesed condition") <<
242                             "set(ONE TRUE)\n"
243                             "set(ZERO FALSE)\n"
244                             "if(( ONE AND ZERO ) OR ( ZERO OR ONE ))\n"
245                                "set(res 1)\n"
246                             "endif(( ONE AND ZERO ) OR ( ZERO OR ONE ))\n"
247                              << cacheValues << results;
248                              
249                              cacheValues.clear();
250     results.clear();
251     results << StringPair("res", "1");
252     results << StringPair("end", "1");
253     QTest::newRow("full conditional") <<
254                             "set(ONE TRUE)\n"
255                             "set(ZERO FALSE)\n"
256                             "if(ONE)\n"
257                                "set(res 1)\n"
258                                "set(res 1)\n"
259                             "else(ONE)\n"
260                                "set(res 0)\n"
261                                "set(res 0)\n"
262                             "endif(ONE)\n"
263                             "set(end 1)\n"
264                              << cacheValues << results;
265                              
266     results.clear();
267     results << StringPair("res", "1");
268     results << StringPair("end", "1");
269     QTest::newRow("full conditional.false") <<
270                             "set(ONE TRUE)\n"
271                             "set(ZERO FALSE)\n"
272                             "if(ZERO)\n"
273                                "set(res 0)\n"
274                                "set(res 0)\n"
275                             "else(ZERO)\n"
276                                "set(res 1)\n"
277                                "set(res 1)\n"
278                             "endif(ZERO)\n"
279                             "set(end 1)\n"
280                              << cacheValues << results;
281                              
282     results.clear();
283     QTest::newRow("no_endif") <<
284                             "set(ZERO FALSE)\n"
285                             "if(ZERO)\n"
286                                "set(res 0)\n"
287                                "set(res 0)\n"
288                              << cacheValues << results;
289                              
290     results.clear();
291     QTest::newRow("no_endwhile") <<
292                             "set(VAR TRUE)\n"
293                             "while(VAR)\n"
294                                "set(res 0)\n"
295                                "set(res 0)\n"
296                                "set(VAR FALSE)\n"
297                              << cacheValues << results;
298                              
299     results.clear();
300     results << StringPair("VAR", "FALSE");
301     QTest::newRow("weirdIf") <<
302                             "set(VAR FALSE)\n"
303                             "if(VAR)\n"
304                             "set(VAR a)\n"
305                             "endif()\n"
306                             << cacheValues << results;
307                             
308     results.clear();
309     results << StringPair("GOOD", "TRUE");
310     QTest::newRow("twoconditions") <<
311                             "set(GOOD FALSE)\n"
312                             "set(aaa ca)\n"
313                             "set(aab co)\n"
314                             "set(b a)\n"
315 //                             "message(STATUS \"${aaa}${aab} should be caco\")\n"
316 //                             "message(STATUS \"${a${b}a}${a${b}b} should be caco\")\n"
317                             "if(\"${a${b}a}${a${b}b}\" STREQUAL caco )\n"
318                             "  set(GOOD TRUE)\n"
319                             "endif()\n"
320                             << cacheValues << results;
321                            
322     results.clear();                        
323     results << StringPair("str", "babababababa");
324     QTest::newRow("replace") <<
325                             "set(str tatatttatatttata)\n"
326                             "string(REGEX REPLACE \"t+\" \"b\" str ${str})\n"
327                             << cacheValues << results;
328                             
329     results.clear();
330     results << StringPair("str", "potatoe\"\npotatoe");
331     QTest::newRow("scaping") <<
332                             "set(str potatoe\\\"\\npotatoe)\n"
333                             << cacheValues << results;
334     results.clear();
335     results << StringPair("str", "123");
336     QTest::newRow("regex") <<
337                             "STRING(REGEX REPLACE \"QT_LIBINFIX *= *([^\\n]*)\" \"\\\\1\" str \"QT_LIBINFIX = 123\")\n"
338                             << cacheValues << results;
339                             
340     //This test should probably be linux-only
341     results.clear();
342     results << StringPair("good", "TRUE");
343     QTest::newRow("ifexists") <<
344                             "set(good FALSE)\n"
345                             "if(EXISTS Makefile)\n" //Relative
346                             "   set(good TRUE)\n"
347                             "endif()\n"
348                             
349                             "if(EXISTS /proc)\n"    //Absolute
350                             "   set(good TRUE)\n"
351                             "else()\n"
352                             "   set(good FALSE)\n"
353                             "endif()\n"
354                             
355                             "if(EXISTS /pppppppp)\n" //Doesn't exist absolute
356                             "   set(good FALSE)\n"
357                             "else()\n"
358                             "   set(good TRUE)\n"
359                             "endif()\n"
360                             
361                             "if(EXISTS MAAAAA)\n" //Doesn't exist relative
362                             "   set(good FALSE)\n"
363                             "else()\n"
364                             "   set(good TRUE)\n"
365                             "endif()\n"
366                             << cacheValues << results;
367     
368     //This test should probably be linux-only
369     results.clear();
370     results << StringPair("output", "/lib");
371     QTest::newRow("get_filename_component") <<
372                             "get_filename_component(output /usr/lib/libdl.so PATH)\n"
373                             << cacheValues << results;
374                             
375     results.clear();
376     QTest::newRow("unfinished function") <<
377                             "function(test)\n"
378                             << cacheValues << results;
379     results.clear();
380
381     
382     results << StringPair("args", "one;two;three;four");
383     results << StringPair("args2", "one;two;\"three;four\"");
384     QTest::newRow("separate arguments") <<
385                             "SET(args \"one two three four\")\n"
386                             "SET(args2 \"one two \\\"three four\\\"\")\n"
387                             "SEPARATE_ARGUMENTS(args)\n"
388                             "SEPARATE_ARGUMENTS(args2)\n"
389                             << cacheValues << results;
390     
391     results.clear();
392     QTest::newRow("break") <<
393                             "while(1)\n"
394                             "break()\n"
395                             "endwhile(1)\n"
396                             << cacheValues << results;
397     
398     results.clear();
399     QTest::newRow("break1") <<
400                             "while(1)\n"
401                             "if(TRUE)\n"
402                                 "break()\n"
403                             "endif(TRUE)\n"
404                             "endwhile(1)\n"
405                             << cacheValues << results;
406 }
407
408 void CMakeProjectVisitorTest::testRun()
409 {
410     QFETCH(QString, input);
411     QFETCH(QList<StringPair>, cache);
412     QFETCH(QList<StringPair>, results);
413     
414     KDevelop::ReferencedTopDUContext fakeContext=
415                     new TopDUContext(IndexedString("test"), SimpleRange(0,0,0,0));
416     DUChain::self()->addDocumentChain(fakeContext);
417     
418     QFile file("cmake_visitor_test");
419     QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
420     
421     QTextStream out(&file);
422     out << input;
423     file.close();
424     CMakeFileContent code=CMakeListsParser::readCMakeFile(file.fileName());
425     file.remove();
426     QVERIFY(code.count() != 0);
427     
428     MacroMap mm;
429     VariableMap vm;
430     CacheValues val;
431     foreach(const StringPair& v, cache)
432         val[v.first]=v.second;
433     
434     vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList("./"));
435     
436     CMakeProjectVisitor v(file.fileName(), fakeContext);
437     v.setVariableMap(&vm);
438     v.setMacroMap(&mm);
439     v.setCacheValues( &val );
440     v.walk(code, 0);
441
442     foreach(const StringPair& vp, results)
443     {
444         CMakeFunctionArgument arg;
445         arg.value=vp.first;
446         
447         QCOMPARE(vm.value(vp.first).join(QString(";")), vp.second);
448     }
449     {
450         KDevelop::DUChainWriteLocker lock(DUChain::lock());
451         DUChain::self()->removeDocumentChain(fakeContext);
452     }
453 }
454
455 void CMakeProjectVisitorTest::testFinder_data()
456 {
457     QTest::addColumn<QString>("module");
458     
459     QTest::newRow("Qt4") << "Qt4";
460     QTest::newRow("KDE4") << "KDE4";
461 }
462
463 void CMakeProjectVisitorTest::testFinder_init()
464 {
465     QPair<VariableMap, QStringList> initials=CMakeParserUtils::initialVariables();
466     modulePath += initials.first.value("CMAKE_MODULE_PATH");
467 //     modulePath += QStringList(CMAKE_TESTS_PROJECTS_DIR "/modules"); //Not used yet
468
469     initialVariables=initials.first;
470     buildstrap=initials.second;
471 }
472
473 void CMakeProjectVisitorTest::testFinder()
474 {
475     QFETCH(QString, module);
476     testFinder_init();
477     
478     KDevelop::ReferencedTopDUContext fakeContext=
479                     new TopDUContext(IndexedString("test"), SimpleRange(0,0,0,0));
480     DUChain::self()->addDocumentChain(fakeContext);
481     
482     QFile file("cmake_visitor_test");
483     QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
484     
485     QTextStream out(&file);
486     out << QString("find_package(%1 REQUIRED)\n").arg(module);
487     file.close();
488     CMakeFileContent code=CMakeListsParser::readCMakeFile(file.fileName());
489     file.remove();
490     QVERIFY(code.count() != 0);
491     
492     MacroMap mm;
493     CacheValues val;
494     VariableMap vm;
495     vm=initialVariables;
496     vm.insert("CMAKE_BINARY_DIR", QStringList("./"));
497     vm.insert("CMAKE_MODULE_PATH", modulePath);
498     
499     foreach(const QString& script, buildstrap)
500     {
501         QString scriptfile=CMakeProjectVisitor::findFile(script, modulePath, QStringList());
502         fakeContext=CMakeParserUtils::includeScript(scriptfile, fakeContext, &vm, &mm,
503                                                     "./", &val, modulePath);
504     }
505     
506     vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList("./"));
507     CMakeProjectVisitor v(file.fileName(), fakeContext);
508     v.setVariableMap(&vm);
509     v.setMacroMap(&mm);
510     v.setCacheValues( &val );
511     v.walk(code, 0);
512     
513     QString foundvar=QString("%1_FOUND").arg(module.toUpper());
514     bool found=CMakeCondition(&v).condition(QStringList(foundvar));
515     if(!found)
516         qDebug() << "result: " << vm.value(foundvar);
517     
518     QVERIFY(found);
519     
520     {
521         KDevelop::DUChainWriteLocker lock(DUChain::lock());
522         DUChain::self()->removeDocumentChain(fakeContext);
523     }
524 }
525
526 void CMakeProjectVisitorTest::testForeachLines()
527 {
528     CMakeFunctionDesc foreachDesc, messageDesc, endForeachDesc;
529     foreachDesc.name = "foreach";
530     foreachDesc.addArguments(QStringList() << "i" << "RANGE" << "10" << "1");
531     messageDesc.name = "message"; // or anything
532     messageDesc.addArguments(QStringList() << "STATUS" << "pyon");
533     endForeachDesc.name = "endforeach";
534
535     CMakeFileContent content;
536     content << messageDesc << foreachDesc << messageDesc << endForeachDesc << messageDesc;
537
538     ForeachAst* ast = static_cast<ForeachAst*>(AstFactory::self()->createAst("foreach"));
539     ast->setContent(content, 1);
540     ast->parseFunctionInfo(foreachDesc);
541
542     VariableMap vm;
543     CMakeProjectVisitor v("somefile", ReferencedTopDUContext());
544     v.setVariableMap(&vm);
545
546     QCOMPARE(v.visit(ast), 3);
547     delete ast;
548 }
549
550
551 #include "cmake_cmakeprojectvisitor_test.moc"