diff --git a/.gitignore b/.gitignore index 597bbbec..a58d38f3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ resources/CMakeFiles resources/MultiMCLauncher.jar *~ *.swp +html/ # Ctags File tags diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dadbad4..be0eced8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -461,6 +461,15 @@ IF(WIN32) ) ENDIF(WIN32) +OPTION(MultiMC_CODE_COVERAGE "Compiles for code coverage" OFF) +IF(MultiMC_CODE_COVERAGE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") +ENDIF(MultiMC_CODE_COVERAGE) + # Tell CMake that MultiMCLauncher.jar is generated. SET_SOURCE_FILES_PROPERTIES(${PROJECT_BINARY_DIR}/depends/launcher/MultiMCLauncher.jar GENERATED) SET_SOURCE_FILES_PROPERTIES(${PROJECT_BINARY_DIR}/depends/javacheck/JavaCheck.jar GENERATED) @@ -471,14 +480,18 @@ CONFIGURE_FILE(generated.qrc.in generated.qrc) QT5_ADD_RESOURCES(GENERATED_QRC ${CMAKE_CURRENT_BINARY_DIR}/generated.qrc) QT5_ADD_RESOURCES(GRAPHICS_QRC graphics.qrc) +# Add common library +ADD_LIBRARY(MultiMC_common STATIC ${MULTIMC_SOURCES} ${MULTIMC_UI} ${GENERATED_QRC} ${GRAPHICS_QRC} ${MULTIMC_RCS}) + # Add executable -ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 - ${MULTIMC_SOURCES} ${MULTIMC_UI} ${GRAPHICS_QRC} ${GENERATED_QRC} ${MULTIMC_RCS}) +ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp) # Link -TARGET_LINK_LIBRARIES(MultiMC xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) +TARGET_LINK_LIBRARIES(MultiMC MultiMC_common) +TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) QT5_USE_MODULES(MultiMC Core Widgets Network Xml ${MultiMC_QT_ADDITIONAL_MODULES}) -ADD_DEPENDENCIES(MultiMC MultiMCLauncher JavaCheck) +QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml ${MultiMC_QT_ADDITIONAL_MODULES}) +ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck) ################################ INSTALLATION AND PACKAGING ################################ @@ -637,3 +650,6 @@ ENDIF() add_custom_target (translations DEPENDS ${QM_FILES}) install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/translations) + +# Tests +add_subdirectory(tests) diff --git a/MultiMC.cpp b/MultiMC.cpp index 2c9e74dd..bf0d9d99 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -9,7 +9,6 @@ #include #include -#include "gui/MainWindow.h" #include "gui/dialogs/VersionSelectDialog.h" #include "logic/lists/InstanceList.h" #include "logic/auth/MojangAccountList.h" @@ -35,7 +34,7 @@ #include "config.h" using namespace Util::Commandline; -MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv), +MultiMC::MultiMC(int &argc, char **argv, const QString &root) : QApplication(argc, argv), m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_CHANNEL, VERSION_BUILD_TYPE} { setOrganizationName("MultiMC"); @@ -136,7 +135,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv), } // change directory - QDir::setCurrent(args["dir"].toString()); + QDir::setCurrent(args["dir"].toString().isEmpty() ? + (root.isEmpty() ? QDir::currentPath() : QDir::current().absoluteFilePath(root)) + : args["dir"].toString()); // init the logger initLogger(); @@ -475,37 +476,5 @@ QString MultiMC::getExitUpdatePath() const return m_updateOnExitPath; } -int main_gui(MultiMC &app) -{ - // show main window - MainWindow mainWin; - mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); - mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); - mainWin.show(); - mainWin.checkSetDefaultJava(); - auto exitCode = app.exec(); - - // Update if necessary. - if (!app.getExitUpdatePath().isEmpty()) - app.installUpdates(app.getExitUpdatePath(), false); - - return exitCode; -} - -int main(int argc, char *argv[]) -{ - // initialize Qt - MultiMC app(argc, argv); - - switch (app.status()) - { - case MultiMC::Initialized: - return main_gui(app); - case MultiMC::Failed: - return 1; - case MultiMC::Succeeded: - return 0; - } -} #include "MultiMC.moc" diff --git a/MultiMC.h b/MultiMC.h index 22cea029..7bfa0023 100644 --- a/MultiMC.h +++ b/MultiMC.h @@ -45,7 +45,7 @@ public: }; public: - MultiMC(int &argc, char **argv); + MultiMC(int &argc, char **argv, const QString &root = QString()); virtual ~MultiMC(); std::shared_ptr settings() diff --git a/depends/settings/CMakeLists.txt b/depends/settings/CMakeLists.txt index 3de1d792..154697f6 100644 --- a/depends/settings/CMakeLists.txt +++ b/depends/settings/CMakeLists.txt @@ -51,6 +51,14 @@ add_definitions(-DLIBSETTINGS_LIBRARY) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +IF(MultiMC_CODE_COVERAGE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") +ENDIF(MultiMC_CODE_COVERAGE) + add_library(libSettings STATIC ${LIBSETTINGS_SOURCES} ${LIBSETTINGS_HEADERS} ${LIBSETTINGS_HEADERS_PRIVATE}) qt5_use_modules(libSettings Core) target_link_libraries(libSettings) diff --git a/depends/util/CMakeLists.txt b/depends/util/CMakeLists.txt index 5c87c644..db7d70e6 100644 --- a/depends/util/CMakeLists.txt +++ b/depends/util/CMakeLists.txt @@ -47,6 +47,14 @@ add_definitions(-DLIBUTIL_LIBRARY) set(CMAKE_POSITION_INDEPENDENT_CODE ON) +IF(MultiMC_CODE_COVERAGE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage") +ENDIF(MultiMC_CODE_COVERAGE) + add_library(libUtil STATIC ${LIBUTIL_SOURCES}) # qt5_use_modules(libUtil Core Network) qt5_use_modules(libUtil Core) diff --git a/logic/auth/MojangAccountList.cpp b/logic/auth/MojangAccountList.cpp index 841552f5..33990662 100644 --- a/logic/auth/MojangAccountList.cpp +++ b/logic/auth/MojangAccountList.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "logger/QsLog.h" @@ -264,6 +265,11 @@ bool MojangAccountList::loadList(const QString &filePath) return false; } + if (!QDir::current().exists(path)) + { + QDir::current().mkpath(path); + } + QFile file(path); // Try to open the file and fail if we can't. @@ -345,6 +351,11 @@ bool MojangAccountList::saveList(const QString &filePath) return false; } + if (!QDir::current().exists(path)) + { + QDir::current().mkpath(path); + } + QLOG_INFO() << "Writing account list to" << path; QLOG_DEBUG() << "Building JSON data structure."; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index b9595578..94481fb9 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -36,11 +36,16 @@ const static int GROUP_FILE_FORMAT_VERSION = 1; InstanceList::InstanceList(const QString &instDir, QObject *parent) : QAbstractListModel(parent), m_instDir(instDir) { + connect(MMC, &MultiMC::aboutToQuit, this, &InstanceList::saveGroupList); + + if (!QDir::current().exists(m_instDir)) + { + QDir::current().mkpath(m_instDir); + } } InstanceList::~InstanceList() { - saveGroupList(); } int InstanceList::rowCount(const QModelIndex &parent) const diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h index 8cd39746..c3bb74cd 100644 --- a/logic/lists/InstanceList.h +++ b/logic/lists/InstanceList.h @@ -29,6 +29,9 @@ class InstanceList : public QAbstractListModel Q_OBJECT private: void loadGroupList(QMap &groupList); + +private +slots: void saveGroupList(); public: diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..fb75765a --- /dev/null +++ b/main.cpp @@ -0,0 +1,38 @@ +#include "MultiMC.h" +#include "gui/MainWindow.h" + +int main_gui(MultiMC &app) +{ + // show main window + MainWindow mainWin; + mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray())); + mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray())); + mainWin.show(); + mainWin.checkSetDefaultJava(); + auto exitCode = app.exec(); + + // Update if necessary. + if (!app.getExitUpdatePath().isEmpty()) + app.installUpdates(app.getExitUpdatePath(), false); + + return exitCode; +} + +int main(int argc, char *argv[]) +{ + // initialize Qt + MultiMC app(argc, argv); + + Q_INIT_RESOURCE(graphics); + Q_INIT_RESOURCE(generated); + + switch (app.status()) + { + case MultiMC::Initialized: + return main_gui(app); + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..20863c73 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,85 @@ +find_package(Qt5 COMPONENTS Test Core Network Widgets) + +include_directories(${MMC_SRC}) + +unset(MultiMC_TESTS) +macro(add_unit_test name) + unset(srcs) + foreach(arg ${testname} ${ARGN}) + list(APPEND srcs ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) + endforeach() + add_executable(tst_${name} ${srcs}) + qt5_use_modules(tst_${name} Test Core Network Widgets) + target_link_libraries(tst_${name} MultiMC_common) + add_test(tst_${name} tst_${name}) + list(APPEND MultiMC_TESTS tst_${name}) +endmacro() + +macro(add_unit_test2 name) + add_unit_test(${name} tst_${name}.cpp) +endmacro() + +# Tests START # + +add_unit_test2(pathutils) +add_unit_test2(userutils) + +# Tests END # + +set(COVERAGE_SOURCE_DIRS + ${MMC_SRC}/logic/* + ${MMC_SRC}/logic/auth/* + ${MMC_SRC}/logic/auth/flows/* + ${MMC_SRC}/logic/lists/* + ${MMC_SRC}/logic/net/* + ${MMC_SRC}/logic/tasks/* + ${MMC_SRC}/gui/* + ${MMC_SRC}/gui/dialogs/* + ${MMC_SRC}/gui/widgets/* + ${MMC_SRC}/depends/settings/include/* + ${MMC_SRC}/depends/settings/src/* + ${MMC_SRC}/depends/util/include/* + ${MMC_SRC}/depends/util/src/* +) + +if(MultiMC_CODE_COVERAGE) + unset(MultiMC_RUN_TESTS) + unset(MultiMC_TEST_COVERAGE_FILES) + + foreach(test ${MultiMC_TESTS}) + add_custom_target(MultiMC_RUN_TEST_${test} + COMMAND lcov -d ${CMAKE_CURRENT_BINARY_DIR} -z -q # clean test + && lcov -d ${MMC_BIN} -z -q # clean common + && lcov -d ${MMC_BIN}/depends/settings/CMakeFiles/libSettings.dir -z -q # clean settings + && lcov -d ${MMC_BIN}/depends/utils/CMakeFiles/libUtil.dir -z -q # clean utils + && ${MMC_BIN}/${test} -o coverage_${test}.out,xml # run test + && lcov -q --checksum -b ${MMC_SRC} -d ${CMAKE_CURRENT_BINARY_DIR} -c -o coverage_${test}.info # generate for test + && lcov -q --checksum -b ${MMC_SRC} -d ${MMC_BIN} -c -o coverage_common.info # generate for common + && lcov -q --checksum -b ${MMC_SRC} -d ${MMC_BIN}/depends/settings/CMakeFiles/libSettings.dir -c -o coverage_settings.info # generate for settings + && lcov -q --checksum -b ${MMC_SRC} -d ${MMC_BIN}/depends/util/CMakeFiles/libUtil.dir -c -o coverage_utils.info # generate for utils + && lcov -q --checksum -b ${MMC_SRC} -d . + -a coverage_${test}.info -a coverage_common.info -a coverage_settings.info -a coverage_utils.info + -o coverage_${test}-combined.info # combine test and common + && lcov -q --checksum -b ${MMC_SRC} --list-full-path --extract coverage_${test}-combined.info ${COVERAGE_SOURCE_DIRS} -o coverage_${test}-stripped.info # strip + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + VERBATIM + DEPENDS ${test} + COMMENT "Running ${test}..." + ) + list(APPEND MultiMC_TEST_COVERAGE_FILES coverage_${test}-stripped.info) + list(APPEND MultiMC_RUN_TESTS MultiMC_RUN_TEST_${test}) + endforeach(test) + + add_custom_target(MultiMC_GENERATE_COVERAGE + DEPENDS ${MultiMC_RUN_TESTS} + COMMENT "Generating coverage files..." + ) + add_custom_target(MultiMC_GENERATE_COVERAGE_HTML + COMMAND genhtml -t "MultiMC 5 Test Coverage" --num-spaces 4 --demangle-cpp --legend -o ${MMC_SRC}/html/coverage ${MultiMC_TEST_COVERAGE_FILES} + DEPENDS MultiMC_GENERATE_COVERAGE + COMMENT "Generating test coverage html..." + ) + add_custom_target(MultiMC_RUN_TESTS DEPENDS MultiMC_GENERATE_COVERAGE_HTML) +endif(MultiMC_CODE_COVERAGE) + +add_subdirectory(data) diff --git a/tests/TestUtil.h b/tests/TestUtil.h new file mode 100644 index 00000000..64ee1675 --- /dev/null +++ b/tests/TestUtil.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include "MultiMC.h" + +struct TestsInternal +{ + static QByteArray readFile(const QString &fileName) + { + QFile f(fileName); + f.open(QFile::ReadOnly); + return f.readAll(); + } +}; + +#define MULTIMC_GET_TEST_FILE(file) TestsInternal::readFile(QFINDTESTDATA( file )) + +#define QTEST_GUILESS_MAIN_MULTIMC(TestObject) \ +int main(int argc, char *argv[]) \ +{ \ + char *argv_[] = { argv[0] }; \ + int argc_ = 1; \ + MultiMC app(argc_, argv_, QDir::temp().absoluteFilePath("MultiMC_Test")); \ + app.setAttribute(Qt::AA_Use96Dpi, true); \ + TestObject tc; \ + return QTest::qExec(&tc, argc, argv); \ +} diff --git a/tests/data/CMakeLists.txt b/tests/data/CMakeLists.txt new file mode 100644 index 00000000..eee5a596 --- /dev/null +++ b/tests/data/CMakeLists.txt @@ -0,0 +1,4 @@ +add_custom_target(MultiMC_Test_Data + ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/tests/data/tst_userutils-test_createShortcut-unix b/tests/data/tst_userutils-test_createShortcut-unix new file mode 100755 index 00000000..1ce3a2bd --- /dev/null +++ b/tests/data/tst_userutils-test_createShortcut-unix @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +TryExec=asdfDest +Exec=asdfDest 'arg1' 'arg2' +Name=asdf +Icon= diff --git a/tests/tst_pathutils.cpp b/tests/tst_pathutils.cpp new file mode 100644 index 00000000..1e4a83bf --- /dev/null +++ b/tests/tst_pathutils.cpp @@ -0,0 +1,76 @@ +#include +#include "TestUtil.h" + +#include "depends/util/include/pathutils.h" + +class PathUtilsTest : public QObject +{ + Q_OBJECT +private +slots: + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_PathCombine1_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + +#if defined(Q_OS_UNIX) + QTest::newRow("unix 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; + QTest::newRow("unix 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; +#elif defined(Q_OS_WIN) + QTest::newRow("win, from C:") << "C:\\abc" << "C:" << "abc\\def"; + QTest::newRow("win 1") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\def" << "ghi\\jkl"; + QTest::newRow("win 2") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; +#endif + } + void test_PathCombine1() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + + QCOMPARE(PathCombine(path1, path2), result); + } + + void test_PathCombine2_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + QTest::addColumn("path3"); + +#if defined(Q_OS_UNIX) + QTest::newRow("unix 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; + QTest::newRow("unix 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; + QTest::newRow("unix 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; + QTest::newRow("unix 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; +#elif defined(Q_OS_WIN) + QTest::newRow("win 1") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc" << "def" << "ghi\\jkl"; + QTest::newRow("win 2") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 3") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; + QTest::newRow("win 4") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; +#endif + } + void test_PathCombine2() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + QFETCH(QString, path3); + + QCOMPARE(PathCombine(path1, path2, path3), result); + } +}; + +QTEST_GUILESS_MAIN_MULTIMC(PathUtilsTest) + +#include "tst_pathutils.moc" diff --git a/tests/tst_userutils.cpp b/tests/tst_userutils.cpp new file mode 100644 index 00000000..62bee985 --- /dev/null +++ b/tests/tst_userutils.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "TestUtil.h" + +#include "depends/util/include/userutils.h" + +class UserUtilsTest : public QObject +{ + Q_OBJECT +private +slots: + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_getDesktop() + { + QCOMPARE(Util::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + } + + void test_createShortcut_data() + { + QTest::addColumn("location"); + QTest::addColumn("dest"); + QTest::addColumn("args"); + QTest::addColumn("name"); + QTest::addColumn("iconLocation"); + QTest::addColumn("result"); + + QTest::newRow("unix") << QDir::currentPath() + << "asdfDest" + << (QStringList() << "arg1" << "arg2") + << "asdf" + << QString() + #if defined(Q_OS_LINUX) + << MULTIMC_GET_TEST_FILE("data/tst_userutils-test_createShortcut-unix") + #elif defined(Q_OS_WIN) + << QString() + #endif + ; + } + + void test_createShortcut() + { + QFETCH(QString, location); + QFETCH(QString, dest); + QFETCH(QStringList, args); + QFETCH(QString, name); + QFETCH(QString, iconLocation); + QFETCH(QByteArray, result); + + QVERIFY(Util::createShortCut(location, dest, args, name, iconLocation)); + QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); + + //QDir().remove(location); + } +}; + +QTEST_GUILESS_MAIN_MULTIMC(UserUtilsTest) + +#include "tst_userutils.moc"