/* KDevelop CMake Support * * Copyright 2008 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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 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 "cmakeprojectvisitortest.h" #include "cmake-test-paths.h" #include "cmakeast.h" #include "cmakeprojectvisitor.h" #include "cmakelistsparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_KDEMAIN_CORE(CMakeProjectVisitorTest) using namespace KDevelop; #undef TRUE //krazy:exclude=captruefalse #undef FALSE //krazy:exclude=captruefalse static QSharedPointer prepareVisitoTestScript(const QString &scriptContent) { QSharedPointer file(new KTemporaryFile); if ( !file->open() ) return QSharedPointer(); QTextStream out(file.data()); out << scriptContent; file->close(); return file; } CMakeProjectVisitorTest::CMakeProjectVisitorTest() : CMakeProjectVisitor( QString(), 0) { } void CMakeProjectVisitorTest::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void CMakeProjectVisitorTest::cleanupTestCase() { TestCore::shutdown(); } void CMakeProjectVisitorTest::init() { fakeContext = new TopDUContext(IndexedString("test"), RangeInRevision(0,0,0,0)); DUChain::self()->addDocumentChain(fakeContext); } void CMakeProjectVisitorTest::cleanup() { KDevelop::DUChainWriteLocker lock(DUChain::lock()); DUChain::self()->removeDocumentChain(fakeContext); } void CMakeProjectVisitorTest::testVariables_data() { QTest::addColumn("input"); QTest::addColumn("result"); QTest::newRow("a variable") << "${MY_VAR}" << QStringList("MY_VAR"); QTest::newRow("env var") << "$ENV{MY_VAR}" << QStringList("MY_VAR"); QTest::newRow("Contains a variable") << "${MY_VAR}/lol" << QStringList("MY_VAR"); QTest::newRow("Contains a variable") << "${yipiee}#include <${it}>\n" << (QStringList("yipiee") << "it"); QTest::newRow("Contains a variable") << "${a}${b}\n" << (QStringList("a") << "b"); QTest::newRow("mess") << "{}{}{}}}}{{{{}${a}\n" << QStringList("a"); QTest::newRow("Nothing") << "aaaa${aaaa" << QStringList(); QTest::newRow("varinvar") << "${${${a}}}" << (QStringList() << "${${a}}" << "${a}" << "a"); QTest::newRow("varsinvar") << "${${a}${b}a" << (QStringList() << "a" << "b"); QTest::newRow("varsinvar") << "${a${b}a}${a${b}a}" << (QStringList() << "a${b}a" << "b" << "a${b}a" << "b"); } void CMakeProjectVisitorTest::testVariables() { QFETCH(QString, input); QFETCH(QStringList, result); QStringList name; QList variables =CMakeProjectVisitor::parseArgument(input); // qDebug() << "kakakaka" << result << variables; QCOMPARE(result.count(), variables.count()); if(!variables.isEmpty()) QCOMPARE(1, variables.last().level); foreach(const CMakeProjectVisitor::IntPair& v, variables) { QString name=input.mid(v.first+1, v.second-v.first-1); if(!result.contains(name)) qDebug() << "not a var:" << name; QVERIFY(result.contains(name)); } } typedef QPair StringPair; Q_DECLARE_METATYPE(QList) void CMakeProjectVisitorTest::testRun_data() { QTest::addColumn("input"); QTest::addColumn >("cache"); QTest::addColumn >("results"); QList cacheValues; cacheValues << StringPair("aaa", "cmd"); cacheValues << StringPair("bbb", "cmd"); cacheValues << StringPair("ccc", "cmd"); cacheValues << StringPair("ddd", "cmd"); cacheValues << StringPair("eee", "cmd"); cacheValues << StringPair("fff", "cmd"); cacheValues << StringPair("ggg", "cmd"); QList results; results << StringPair("aaa", "cmd"); results << StringPair("bbb", "cmd"); results << StringPair("ccc", "cmd"); results << StringPair("ddd", "script"); results << StringPair("eee", "cmd"); results << StringPair("fff", "cmd"); results << StringPair("ggg", "cmd"); QTest::newRow("cache") << "project(simpletest)\n" "cmake_minimum_required(VERSION 2.6)\n" "find_file(aaa stdio.h /usr/include)\n" "set(bbb script CACHE STRING HELLO)\n" "set(ccc script CACHE STRING HELLO FORCE)\n" "set(ddd script)\n" "#message(STATUS \"ooooo- ${aaa} ${bbb} ${ccc} ${ddd}\")\n" "find_path(eee stdio.h /usr/include)\n" "find_library(fff stdio.h /usr/include)\n" "find_program(ggg gcc /usr/gcc)\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("FOOBAR", "ORT Basket Is Strange ABORT"); results << StringPair("RES", "Ok"); results << StringPair("BARFOO", "ORT Is Basket Strange? ABORT"); results << StringPair("BARFOO_MATCH", "Basket Is"); results << StringPair("BARFOO_MATCHALL", "Basket Is;Basket Is;Basket Is;Basket Is"); QTest::newRow("string") << "set(FOOBAR \"ORT Basket Is Strange ABORT\")\n" "if( FOOBAR MATCHES \"^ORT Bas\")\n" " set(RES Ok)\n" "else( FOOBAR MATCHES \"^ORT Bas\")\n" " set(RES Wrong)\n" "endif( FOOBAR MATCHES \"^ORT Bas\")\n" "string( REGEX REPLACE \"Basket ([a-zA-Z]*) ([a-zA-Z]*)\" \"\\\\1 Basket \\\\2?\" BARFOO ${FOOBAR})\n" "string( REGEX MATCH \"Basket Is\" BARFOO_MATCH ${FOOBAR} ${RES} ${FOOBAR})\n" "string( REGEX MATCHALL \"Basket Is\" BARFOO_MATCHALL ${FOOBAR} \"${FOOBAR}${RES}${FOOBAR}\" ${FOOBAR})\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("kkk", "abcdef"); QTest::newRow("abc") << "set(a abc)\n" "set(b def)\n" "SET(kkk \"${a}${b}\")\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("kkk", "abcdef"); QTest::newRow("defabc") << "set(a abc)\nset(b def)\nSET(kkk \"${kkk}${a}\")\nSET(kkk \"${kkk}${b}\")\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("_INCLUDE_FILES", "#include \n" "#include \n" "#include \n"); QTest::newRow("foreach") << "set(_HEADER a b c)\n" "FOREACH (it ${_HEADER})\n" " SET(_INCLUDE_FILES \"${_INCLUDE_FILES}#include <${it}>\n\")\n" "ENDFOREACH (it)\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("b", "abc"); results << StringPair("c", "def"); QTest::newRow("semicolons1") << "set(a abc;def)\n" "LIST(GET a 1 c)\nLIST(GET a 0 b)\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("a", "potatoe"); results << StringPair("b", "def"); QTest::newRow("varinvar") << "set(a potatoe)\n" "set(potatoe \"abc\")\n" "set(abc \"def\")\n" "set(b \"${${${a}}}\")\n)" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("b", "k"); QTest::newRow("envCC") << "set(a $ENV{PATH})\n" "if(DEFINED a)\n" " set(b k)\n" "endif(DEFINED a)\n"<< cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "${caca}"); QTest::newRow("strange_var") << "set(caca aaaa)\n" "set(v1 \"{ca\")\n" "set(v2 \"ca}\")\n" "set(res \"$${v1}${v2}\")\"\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "222aaaa333"); QTest::newRow("concatenation") << "set(tt aaaa)\n" "set(res \"222${tt}333\")\"\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "222aaaa333"); QTest::newRow("invar_concatenation") << "set(abc aaaa)\n" "set(kk b)\n" "set(res \"222${a${kk}c}333\")\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "oooaaaa"); QTest::newRow("composing") << "set(tt aaaa)\n" "set(a t)\n" "set(b t)\n" "set(res ooo${${a}${b}})\"\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "aaaaaa"); QTest::newRow("composing1") << "set(tt aaaaa)\n" "set(a t)\n" "set(res \"${${a}${a}}a\")\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("_deps", ""); QTest::newRow("mad") << "set(_deps tamare)\n" "aux_source_directory(/tmp _deps)\n"//any unimplemented method "set(_deps ${_deps})\n" "set(${${a}${b}a 33)\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "1"); QTest::newRow("parenthesed condition") << "set(ONE TRUE)\n" "set(ZERO FALSE)\n" "if(( ONE AND ZERO ) OR ( ZERO OR ONE ))\n" "set(res 1)\n" "endif(( ONE AND ZERO ) OR ( ZERO OR ONE ))\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("res", "1"); results << StringPair("end", "1"); QTest::newRow("full conditional") << "set(ONE TRUE)\n" "set(ZERO FALSE)\n" "if(ONE)\n" "set(res 1)\n" "set(res 1)\n" "else(ONE)\n" "set(res 0)\n" "set(res 0)\n" "endif(ONE)\n" "set(end 1)\n" << cacheValues << results; results.clear(); results << StringPair("res", "1"); results << StringPair("end", "1"); QTest::newRow("full conditional.false") << "set(ONE TRUE)\n" "set(ZERO FALSE)\n" "if(ZERO)\n" "set(res 0)\n" "set(res 0)\n" "else(ZERO)\n" "set(res 1)\n" "set(res 1)\n" "endif(ZERO)\n" "set(end 1)\n" << cacheValues << results; results.clear(); QTest::newRow("no_endif") << "set(ZERO FALSE)\n" "if(ZERO)\n" "set(res 0)\n" "set(res 0)\n" << cacheValues << results; results.clear(); QTest::newRow("nested_problem_string") << "if (NOT A)\n" "foreach (moc_file ${B} ${C})\n" "\"\n" "endforeach ()\n" "endif ()\n" << cacheValues << results; results.clear(); QTest::newRow("no_endwhile") << "set(VAR TRUE)\n" "while(VAR)\n" "set(res 0)\n" "set(res 0)\n" "set(VAR FALSE)\n" << cacheValues << results; results.clear(); results << StringPair("VAR", "FALSE"); QTest::newRow("weirdIf") << "set(VAR FALSE)\n" "if(VAR)\n" "set(VAR a)\n" "endif()\n" << cacheValues << results; results.clear(); results << StringPair("GOOD", "TRUE"); QTest::newRow("twoconditions") << "set(GOOD FALSE)\n" "set(aaa ca)\n" "set(aab co)\n" "set(b a)\n" // "message(STATUS \"${aaa}${aab} should be caco\")\n" // "message(STATUS \"${a${b}a}${a${b}b} should be caco\")\n" "if(\"${a${b}a}${a${b}b}\" STREQUAL caco )\n" " set(GOOD TRUE)\n" "endif()\n" << cacheValues << results; results.clear(); results << StringPair("str", "babababababa"); QTest::newRow("replace") << "set(str tatatttatatttata)\n" "string(REGEX REPLACE \"t+\" \"b\" str ${str})\n" << cacheValues << results; results.clear(); results << StringPair("str", "potatoe\"\npotatoe"); QTest::newRow("scaping") << "set(str potatoe\\\"\\npotatoe)\n" << cacheValues << results; results.clear(); results << StringPair("str", "123"); QTest::newRow("regex") << "STRING(REGEX REPLACE \"QT_LIBINFIX *= *([^\\n]*)\" \"\\\\1\" str \"QT_LIBINFIX = 123\")\n" << cacheValues << results; //This test should probably be linux-only results.clear(); results << StringPair("good", "TRUE"); QTest::newRow("ifexists") << "set(good FALSE)\n" "if(EXISTS Makefile)\n" //Relative " set(good TRUE)\n" "endif()\n" "if(EXISTS /proc)\n" //Absolute " set(good TRUE)\n" "else()\n" " set(good FALSE)\n" "endif()\n" "if(EXISTS /pppppppp)\n" //Doesn't exist absolute " set(good FALSE)\n" "else()\n" " set(good TRUE)\n" "endif()\n" "if(EXISTS MAAAAA)\n" //Doesn't exist relative " set(good FALSE)\n" "else()\n" " set(good TRUE)\n" "endif()\n" << cacheValues << results; //This test should probably be linux-only results.clear(); results << StringPair("output", "/usr/lib"); QTest::newRow("get_filename_component") << "get_filename_component(output /usr/lib/libdl.so PATH)\n" << cacheValues << results; results.clear(); results << StringPair("output", "usr/lib"); QTest::newRow("get_filename_component2") << "get_filename_component(output usr/lib/libdl.so PATH)\n" << cacheValues << results; results.clear(); QTest::newRow("unfinished function") << "function(test)\n" << cacheValues << results; results.clear(); results << StringPair("args", "one;two;three;four"); results << StringPair("args2", "one;two;\"three;four\""); QTest::newRow("separate arguments") << "SET(args \"one two three four\")\n" "SET(args2 \"one two \\\"three four\\\"\")\n" "SEPARATE_ARGUMENTS(args)\n" "SEPARATE_ARGUMENTS(args2)\n" << cacheValues << results; results.clear(); QTest::newRow("break") << "while(1)\n" "break()\n" "endwhile(1)\n" << cacheValues << results; results.clear(); QTest::newRow("break1") << "while(1)\n" "if(TRUE)\n" "break()\n" "endif(TRUE)\n" "endwhile(1)\n" << cacheValues << results; cacheValues << StringPair("VAR", "OFF"); results.clear(); results << StringPair("val", "TRUE"); QTest::newRow("option") << "option(VAR \"something\" ON)\n" "if(NOT VAR)\n" "set(val TRUE)\n" "endif(NOT VAR)\n" << cacheValues << results; cacheValues.clear(); results.clear(); QTest::newRow("unfinished while") << "while(1)\n" << cacheValues << results; cacheValues.clear(); results.clear(); QTest::newRow("unfinished foreach") << "foreach(VAR 1)\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("X", "123"); QTest::newRow("var scope") << "function(HOLA)\n" " set(X something_else)\n" "endfunction(HOLA)\n" "set(X 123)" "HOLA()\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("X", "something_else"); QTest::newRow("var scope parent") << "function(HOLA)\n" " set(X something_else PARENT_SCOPE)\n" "endfunction(HOLA)\n" "set(X 123)\n" "HOLA()\n" << cacheValues << results; cacheValues.clear(); results.clear(); results << StringPair("X", "something_else"); QTest::newRow("var scope cache") << "function(HOLA)\n" " set(X something_else CACHE STRING lala)\n" "endfunction(HOLA)\n" "set(X 123)\n" "HOLA()\n" << cacheValues << results; results.clear(); QTest::newRow("reducedemptyvars") << "string(REPLACE ${A} ${B} X ${A})\n" << cacheValues << results; results.clear(); results << StringPair("result", "hello"); QTest::newRow("library alias") << "add_library(mylib file.cpp)\n" "add_library(My::Lib ALIAS mylib)\n" "set_target_properties(mylib PROPERTIES fu hello)\n" "get_target_property(result My::Lib fu)\n" << cacheValues << results; results.clear(); results << StringPair("result", ""); QTest::newRow("empty set with scope") << "set(result ${nonexistant} PARENT_SCOPE)\n" << cacheValues << results; } void CMakeProjectVisitorTest::testRun() { QFETCH(QString, input); QFETCH(QList, cache); QFETCH(QList, results); QSharedPointer file = prepareVisitoTestScript(input); QVERIFY(!file.isNull()); CMakeFileContent code=CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); MacroMap mm; VariableMap vm; CacheValues val; foreach(const StringPair& v, cache) val[v.first]=v.second; vm.insert("CMAKE_SOURCE_DIR", QStringList("./")); vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList("./")); CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&vm); v.setMacroMap(&mm); v.setCacheValues( &val ); v.walk(code, 0); foreach(const StringPair& vp, results) { CMakeFunctionArgument arg; arg.value=vp.first; QCOMPARE(v.variableValue(vp.first).join(QString(";")), vp.second); } } void CMakeProjectVisitorTest::testFinder_data() { QTest::addColumn("module"); QTest::addColumn("args"); QTest::newRow("ZLIB") << "ZLIB" << QString(); QTest::newRow("PNG") << "PNG" << QString(); QTest::newRow("Qt4") << "Qt4" << QString(); QTest::newRow("Qt4comp") << "Qt4" << QString("COMPONENTS QtCore QtGui"); QTest::newRow("KDE4") << "KDE4" << QString(); QTest::newRow("Phonon") << "Phonon" << QString(); QTest::newRow("Automoc4") << "Automoc4" << QString(); QTest::newRow("Boost") << "Boost" << QString("1.39"); QTest::newRow("TestLib") << "TestLib" << QString(); QTest::newRow("TestLib64") << "TestLib64" << QString(); // QTest::newRow("Eigen2") << "Eigen2" << QString(); // QTest::newRow("Exiv2") << "Exiv2" << QString(); // QTest::newRow("QtGStreamer") << "QtGStreamer" << QString(); //commented because it might not be installed } void CMakeProjectVisitorTest::testFinder_init() { QPair initials=CMakeParserUtils::initialVariables(); modulePath.clear(); modulePath += initials.first.value("CMAKE_MODULE_PATH"); modulePath += CMAKE_INSTALLED_MODULES; // modulePath += QStringList(CMAKE_TESTS_PROJECTS_DIR "/modules"); //Not used yet initialVariables=initials.first; buildstrap=initials.second; } void CMakeProjectVisitorTest::testFinder() { QFETCH(QString, module); QFETCH(QString, args); testFinder_init(); QSharedPointer file = prepareVisitoTestScript(QString("find_package(%1 REQUIRED %2)\n").arg(module).arg(args)); QVERIFY(!file.isNull()); CMakeFileContent code=CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); CMakeProjectData data; data.vm=initialVariables; data.vm.insert("CMAKE_SOURCE_DIR", QStringList("./")); data.vm.insert("CMAKE_BINARY_DIR", QStringList("./")); data.vm.insert("CMAKE_MODULE_PATH", modulePath); data.vm.insert("CMAKE_PREFIX_PATH", QString::fromLatin1(TEST_PREFIX_PATH).split(';', QString::SkipEmptyParts)); foreach(const QString& script, buildstrap) { QString scriptfile=CMakeProjectVisitor::findFile(script, modulePath, QStringList()); fakeContext=CMakeParserUtils::includeScript(scriptfile, fakeContext, &data, "./", QMap()); } data.vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList("./")); CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&data.vm); v.setMacroMap(&data.mm); v.setCacheValues( &data.cache ); QMap env; env["PATH"] = QString::fromLatin1(CMAKE_TESTS_PROJECTS_DIR "/bin:") + QString::fromLatin1(qgetenv("PATH")); env["CMAKE_PREFIX_PATH"] = QString::fromLatin1(TEST_ENV_PREFIX_PATH); env["CMAKE_INCLUDE_PATH"] = QString::fromLatin1(TEST_ENV_INCLUDE_PATH); env["CMAKE_LIBRARY_PATH"] = QString::fromLatin1(TEST_ENV_LIBRARY_PATH); v.setEnvironmentProfile( env ); CMakeProperties props; props[GlobalProperty][QString()]["FIND_LIBRARY_USE_LIB64_PATHS"] = QStringList() << "TRUE"; v.setProperties( props ); v.walk(code, 0); QString foundvar=QString("%1_FOUND").arg(module); bool found=CMakeCondition(&v).condition(QStringList(foundvar)) || CMakeCondition(&v).condition(QStringList(foundvar.toUpper())); if(!found) qDebug() << "result: " << data.vm.value(foundvar); QVERIFY(found); } void CMakeProjectVisitorTest::testGlobs_data() { // This test case covers some usages of file(GLOB ...) and file(GLOB_RECURSE ...) in the way // they're currently supported in KDevelop. The following things are not implemented yet: // 1. file(GLOB_RECURSE ...) behavior regarding symlinks must depend on the cmake policy CMP0009, // but policies are not implemented yet. Current implementation works according to cmake 2.8 with CMP0009 set to NEW. // 2. CMP0009 is automatically set to OLD unless cmake_minimum_required(2.6.3) and higher is specified. // Because cmake_minimum_required is not implemented yet, it also doesn't affect GLOB_RECURSE behavior. // // This test covers implemented parts only. QTest::addColumn("input"); QTest::addColumn("files"); QTest::addColumn >("symlinks"); QTest::addColumn("expectedFiles"); { QStringList files; files << "subdir/a.cpp"; files << "subdir/b.cpp"; files << "subdir/aa.c"; files << "subdir/bb.cpp_"; files << "subdir/1/c.cpp"; files << "subdir/1/d.cpp"; files << "2/e.cpp"; files << "2/f.cpp"; QList symlinks; symlinks << StringPair("2", "subdir/2"); QStringList expectedResults; expectedResults << "subdir/a.cpp"; expectedResults << "subdir/b.cpp"; QTest::newRow("glob_simple") << "project(simpletest)\n" // "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB RESULT \"subdir/*.cpp\")\n" "message(STATUS \"RESULT: ${RESULT}\")\n" << files << symlinks << expectedResults; } { QStringList files; files << "subdir/a.cpp"; files << "subdir/b.cpp"; files << "subdir/1/c.cpp"; files << "subdir/1/d.cpp"; files << "subdir1/e.cpp"; files << "subdir1/f.cpp"; files << "subdir1/2/g.cpp"; files << "subdir1/2/h.cpp"; files << "1/j.cpp"; files << "1/k.cpp"; files << "2/l.cpp"; files << "2/n.cpp"; QList symlinks; symlinks << StringPair("1", "subdir1/1"); symlinks << StringPair("2", "subdir/2"); QStringList expectedResults; expectedResults << "subdir/a.cpp"; expectedResults << "subdir/b.cpp"; expectedResults << "subdir1/e.cpp"; expectedResults << "subdir1/f.cpp"; QTest::newRow("glob_advanced_relative") << "project(simpletest)\n" // "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB RESULT RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} \"subd?r*/*.cpp\")\n" "message(STATUS \"RESULT:\" ${RESULT})\n" << files << symlinks << expectedResults; } { QStringList files; files << "subdir/a.cpp"; files << "subdir/b.cpp"; files << "subdir/aa.c"; files << "subdir/bb.cpp_"; files << "subdir/1/c.cpp"; files << "subdir/1/d.cpp"; files << "subdir1/e.cpp"; files << "1/j.cpp"; files << "1/k.cpp"; files << "2/l.cpp"; files << "2/n.cpp"; QList symlinks; symlinks << StringPair("1", "subdir1/1"); symlinks << StringPair("2", "subdir/2"); QStringList expectedResults; expectedResults << "subdir/a.cpp"; expectedResults << "subdir/b.cpp"; expectedResults << "subdir/1/c.cpp"; expectedResults << "subdir/1/d.cpp"; QTest::newRow("glob_recurse_1") << "project(simpletest)\n" // "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB_RECURSE RESULT \"subdir/*.cpp\")\n" "message(STATUS \"RESULT:\" ${RESULT})\n" << files << symlinks << expectedResults; } { QStringList files; files << "subdir/a.cpp"; files << "subdir1/e.cpp"; files << "subdir1/f.cpp"; files << "subdir1/2/g.cpp"; files << "subdir1/2/h.cpp"; files << "1/j.cpp"; files << "1/k.cpp"; files << "2/l.cpp"; files << "2/n.cpp"; QList symlinks; symlinks << StringPair("1", "subdir1/1"); symlinks << StringPair("2", "subdir/2"); QStringList expectedResults; expectedResults << "subdir1/e.cpp"; expectedResults << "subdir1/f.cpp"; expectedResults << "subdir1/2/g.cpp"; expectedResults << "subdir1/2/h.cpp"; expectedResults << "subdir1/1/j.cpp"; expectedResults << "subdir1/1/k.cpp"; QTest::newRow("glob_recurse_2_relative") << "project(simpletest)\n" "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB_RECURSE RESULT FOLLOW_SYMLINKS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} \"subdir1/*.cpp\")\n" "message(STATUS \"RESULT:\" ${RESULT})\n" << files << symlinks << expectedResults; } { QStringList files; files << "subdir/aaa/a.cpp"; files << "subdir/bbb/b.cpp"; files << "subdir/CMakeLists.txt"; QList symlinks; QStringList expectedResults; expectedResults << "subdir/aaa"; QTest::newRow("glob_directories") << "project(simpletest)\n" // "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB RESULT \"subdir/a*\")\n" "message(STATUS \"RESULT: ${RESULT}\")\n" << files << symlinks << expectedResults; } { QStringList files; files << "subdir/aaa/a.cpp"; files << "subdir/bbb/b.cpp"; files << "subdir/CMakeLists.txt"; QList symlinks; QStringList expectedResults; expectedResults << "subdir/aaa"; expectedResults << "subdir/bbb"; QTest::newRow("glob_negation") << "project(simpletest)\n" // "cmake_minimum_required(VERSION 2.8)\n" "file(GLOB RESULT \"subdir/[!.]*\")\n" "message(STATUS \"RESULT: ${RESULT}\")\n" << files << symlinks << expectedResults; } } void CMakeProjectVisitorTest::testGlobs() { QFETCH(QString, input); QFETCH(QStringList, files); QFETCH(QList, symlinks); QFETCH(QStringList, expectedFiles); KTempDir dir; foreach(QString relativeFilePath, files) { QString fileName = dir.name() + relativeFilePath; QFile file(fileName); QDir fileDir(fileName.left(fileName.lastIndexOf('/'))); if (!fileDir.exists()) QVERIFY2(fileDir.mkpath(fileDir.path()), ("Failed to create dir " + fileDir.path()).toLatin1()); QVERIFY2(file.open(QIODevice::WriteOnly | QIODevice::Truncate), ("Failed to create file" + fileName).toLatin1()); file.close(); //write nothing } foreach(StringPair pair, symlinks) { QFile file(dir.name() + pair.first); QVERIFY2(file.exists(), ("File doesn't exist: " + file.fileName()).toLatin1()); QVERIFY2(file.link(dir.name() + pair.second), ("Failed to create a link: " + dir.name() + pair.second).toLatin1()); } QSharedPointer file = prepareVisitoTestScript(input); QVERIFY(!file.isNull()); CMakeFileContent code = CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); MacroMap mm; VariableMap vm; CacheValues val; vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList(dir.name())); CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&vm); v.setMacroMap(&mm); v.setCacheValues( &val ); v.walk(code, 0); VariableMap::const_iterator it = v.variables()->constFind("RESULT"); QVERIFY2(it != v.variables()->constEnd(), "RESULT variable doesn't exist"); QStringList filesFound = it.value(); QDir baseDir(dir.name()); for (int i = 0; i < filesFound.size(); i++) { QString file = filesFound[i]; if (!QDir::isRelativePath(file)) filesFound[i] = baseDir.relativeFilePath(file); } foreach(QString file, filesFound.toSet().subtract(expectedFiles.toSet())) { qWarning() << "This file was found, but it shouldn't: " << file; } foreach(QString file, expectedFiles.toSet().subtract(filesFound.toSet())) { qWarning() << "This file wasn't found: " << file; } } void CMakeProjectVisitorTest::testForeachLines() { CMakeFunctionDesc foreachDesc, messageDesc, endForeachDesc; foreachDesc.name = "foreach"; foreachDesc.addArguments(QStringList() << "i" << "RANGE" << "10" << "1"); messageDesc.name = "message"; // or anything messageDesc.addArguments(QStringList() << "STATUS" << "pyon"); endForeachDesc.name = "endforeach"; CMakeFileContent content; content << messageDesc << foreachDesc << messageDesc << endForeachDesc << messageDesc; ForeachAst* ast = static_cast(AstFactory::self()->createAst("foreach")); ast->setContent(content, 1); ast->parseFunctionInfo(foreachDesc); VariableMap vm; CMakeProjectVisitor v("somefile", ReferencedTopDUContext()); v.setVariableMap(&vm); QCOMPARE(v.visit(ast), 3); delete ast; } void CMakeProjectVisitorTest::testTargetProperties_data() { QTest::addColumn("input"); QTest::addColumn("target"); QTest::addColumn >("results"); QList results; results.clear(); results << StringPair("RUNTIME_OUTPUT_DIRECTORY", "./build/bin"); QTest::newRow("custom runtime dir") << "add_executable(myprog file.cpp)\n" "set_target_properties(myprog PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)" << "myprog" << results; results.clear(); results << StringPair("LIBRARY_OUTPUT_DIRECTORY", "./build/lib"); QTest::newRow("custom lib dir") << "add_library(mylib file.cpp)\n" "set_target_properties(mylib PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)" << "mylib" << results; results.clear(); results << StringPair("RUNTIME_OUTPUT_DIRECTORY", "./build/bin"); QTest::newRow("custom global runtime dir") << "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\n" "add_executable(myprog file.cpp)\n" << "myprog" << results; results.clear(); results << StringPair("LIBRARY_OUTPUT_DIRECTORY", "./build/lib"); QTest::newRow("custom global lib dir") << "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n" "add_library(mylib file.cpp)\n" << "mylib" << results; results.clear(); results << StringPair("RUNTIME_OUTPUT_DIRECTORY", "./build/subdir/bin"); QTest::newRow("override global runtime dir") << "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\n" "add_executable(myprog file.cpp)\n" "set_target_properties(myprog PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)" << "myprog" << results; results.clear(); results << StringPair("LIBRARY_OUTPUT_DIRECTORY", "./build/subdir/lib"); QTest::newRow("override global lib dir") << "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\n" "add_library(mylib file.cpp)\n" "set_target_properties(mylib PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)" << "mylib" << results; } void CMakeProjectVisitorTest::testTargetProperties() { QFETCH(QString, input); QFETCH(QString, target); QFETCH(QList, results); QSharedPointer file = prepareVisitoTestScript(input); QVERIFY(!file.isNull()); CMakeFileContent code=CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); MacroMap mm; VariableMap vm; CacheValues val; vm.insert("CMAKE_SOURCE_DIR", QStringList("./")); vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList("./subdir")); vm.insert("CMAKE_BINARY_DIR", QStringList("./build")); vm.insert("CMAKE_CURRENT_BINARY_DIR", QStringList("./build/subdir")); // linux exe and lib prefixes and suffixes by default vm.insert("CMAKE_EXECUTABLE_SUFFIX", QStringList("")); vm.insert("CMAKE_LIBRARY_PREFIX", QStringList("lib")); vm.insert("CMAKE_LIBRARY_SUFFIX", QStringList(".so")); CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&vm); v.setMacroMap(&mm); v.setCacheValues(&val); v.walk(code, 0); foreach(const StringPair& vp, results) QCOMPARE(v.properties()[TargetProperty][target][vp.first].join(QString(";")), vp.second); } void CMakeProjectVisitorTest::testBug335803_data() { QTest::addColumn("input"); QTest::addColumn("targetNames"); // variables inside functions from cmake_parse_arguments are not unset correctly // this seems to boil down to set(${prefix}_${name} ${${prefix}_${name}} PARENT_SCOPE) // not being handled correctly const QLatin1String input( "project(cmake_parse_arguments_bug)\n" "%%GET_SINGLE_ARG_FUNC%%\n" "# this bug prevents ecm_add_test(s) from working\n" "function(echo _arg)\n" " get_single_arg(ECHO \"TARGET_NAME\" ${ARGN})\n" " if(ECHO_TARGET_NAME)\n" " set(_target ${ECHO_TARGET_NAME})\n" " else()\n" " set(_target ${_arg})\n" " endif()\n" " add_custom_target(echo-${_target} ALL VERBATIM COMMAND \"echo\" \"${_arg}\")\n" "endfunction()\n" "echo(a)\n" "echo(b)\n" "echo(c TARGET_NAME custom_name)\n" "echo(d)\n" ); // this one works since it uses set(${prefix}_${name} ${_result} PARENT_SCOPE) const QLatin1String getSingleArg1( "function(get_single_arg _prefix _arg)\n" " set(_result)\n" " if(\"${ARGV2}\" STREQUAL \"${_arg}\")\n" " set(_result ${ARGV3})\n" " endif()\n" " set(${_prefix}_TARGET_NAME ${_result} PARENT_SCOPE)\n" "endfunction()\n" ); // this one didn't work before Bug 335803 was fixed because set(${prefix}_${name} ${${prefix}_${name}} PARENT_SCOPE) const QLatin1String getSingleArg2( "function(get_single_arg _prefix _arg)\n" " set(${_prefix}_${_arg})\n" " if(\"${ARGV2}\" STREQUAL \"${_arg}\")\n" " set(${_prefix}_${_arg} ${ARGV3})\n" " endif()\n" " set(${_prefix}_${_arg} ${${_prefix}_${_arg}} PARENT_SCOPE)\n" "endfunction()" ); QStringList targetNames; targetNames << QLatin1String("echo-a") << QLatin1String("echo-b") << QLatin1String("echo-custom_name") << QLatin1String("echo-d"); QTest::newRow("okay-before-fix") << QString(input).replace(QLatin1String("%%GET_SINGLE_ARG_FUNC%%"), getSingleArg1) << targetNames; QTest::newRow("okay-after-fix") << QString(input).replace(QLatin1String("%%GET_SINGLE_ARG_FUNC%%"), getSingleArg2) << targetNames; } void CMakeProjectVisitorTest::testBug335803() { QFETCH(QString, input); QFETCH(QStringList, targetNames); QSharedPointer file = prepareVisitoTestScript(input); QVERIFY(!file.isNull()); CMakeFileContent code = CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); MacroMap mm; VariableMap vm; CacheValues val; CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&vm); v.setMacroMap(&mm); v.setCacheValues(&val); v.walk(code, 0); QStringList foundTargets; foreach(const Target& t, v.targets()) { foundTargets << t.name; } foundTargets.sort(); targetNames.sort(); QCOMPARE(foundTargets, targetNames); } void CMakeProjectVisitorTest::testSetProperty_data() { QTest::addColumn("input"); QTest::addColumn("type"); QTest::addColumn("category"); QTest::addColumn("name"); QTest::addColumn("value"); PropertyType type = GlobalProperty; QString category = QString(); QString name = "foo"; QTest::newRow("global") << "set_property(GLOBAL PROPERTY foo bar)" << type << category << name << QStringList{"bar"}; QTest::newRow("global-append") << "set_property(GLOBAL PROPERTY foo bar)\nset_property(GLOBAL APPEND PROPERTY foo baz)" << type << category << name << QStringList{"bar", "baz"}; QTest::newRow("global-append_string") << "set_property(GLOBAL PROPERTY foo bar)\nset_property(GLOBAL APPEND_STRING PROPERTY foo baz)" << type << category << name << QStringList{"barbaz"}; } void CMakeProjectVisitorTest::testSetProperty() { QFETCH(QString, input); QFETCH(PropertyType, type); QFETCH(QString, category); QFETCH(QString, name); QFETCH(QStringList, value); QSharedPointer file = prepareVisitoTestScript(input); QVERIFY(!file.isNull()); CMakeFileContent code = CMakeListsParser::readCMakeFile(file->fileName()); QVERIFY(code.count() != 0); MacroMap mm; VariableMap vm; CacheValues val; CMakeProjectVisitor v(file->fileName(), fakeContext); v.setVariableMap(&vm); v.setMacroMap(&mm); v.setCacheValues(&val); v.walk(code, 0); QVERIFY(v.properties().contains(type)); QVERIFY(v.properties().value(type).contains(category)); QVERIFY(v.properties().value(type).value(category).contains(name)); QCOMPARE(v.properties().value(type).value(category).value(name), value); }