diff --git a/Tests/auxiliary/testksuserdb.h b/Tests/auxiliary/testksuserdb.h --- a/Tests/auxiliary/testksuserdb.h +++ b/Tests/auxiliary/testksuserdb.h @@ -27,6 +27,8 @@ void init(); void cleanup(); + void testInitializeDB(); + void testCreateScopes_data(); void testCreateScopes(); void testCreateEyepieces_data(); diff --git a/Tests/auxiliary/testksuserdb.cpp b/Tests/auxiliary/testksuserdb.cpp --- a/Tests/auxiliary/testksuserdb.cpp +++ b/Tests/auxiliary/testksuserdb.cpp @@ -8,18 +8,34 @@ version 2 of the License, or (at your option) any later version. */ +#include "ksuserdb.h" +#include "kspaths.h" + #include "testksuserdb.h" TestKSUserDB::TestKSUserDB(QObject *parent) : QObject(parent) { } void TestKSUserDB::initTestCase() { + // Ensure we are in test mode (user .qttest) + QStandardPaths::setTestModeEnabled(true); + QVERIFY(QStandardPaths::isTestModeEnabled()); + + // Remove the user folder that may eventually exist + QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).removeRecursively(); + QVERIFY(!QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()); } void TestKSUserDB::cleanupTestCase() { + // Ensure we are in test mode (user .qttest) + QVERIFY(QStandardPaths::isTestModeEnabled()); + + // Remove the user folder that may eventually exist + QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).removeRecursively(); + QVERIFY(!QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()); } void TestKSUserDB::init() @@ -30,6 +46,21 @@ { } +void TestKSUserDB::testInitializeDB() +{ + KSUserDB * testDB = new KSUserDB(); + QVERIFY(nullptr != testDB); + + // If the KStars folder does not exist, database cannot be created and the app cannot start + QVERIFY(!testDB->Initialize()); + + // So create the KStars folder and retry + QVERIFY(QDir().mkpath(KSPaths::writableLocation(QStandardPaths::GenericDataLocation))); + QVERIFY(testDB->Initialize()); + + delete testDB; +} + void TestKSUserDB::testCreateScopes_data() { } diff --git a/Tests/kstars_ui/CMakeLists.txt b/Tests/kstars_ui/CMakeLists.txt --- a/Tests/kstars_ui/CMakeLists.txt +++ b/Tests/kstars_ui/CMakeLists.txt @@ -1,6 +1,8 @@ SET(KSTARS_UI_TESTS_SRC kstars_ui_tests.cpp + test_kstars_startup.cpp + test_ekos_wizard.cpp test_ekos.cpp test_ekos_focus.cpp test_ekos_simulator.cpp) diff --git a/Tests/kstars_ui/kstars_ui_tests.h b/Tests/kstars_ui/kstars_ui_tests.h --- a/Tests/kstars_ui/kstars_ui_tests.h +++ b/Tests/kstars_ui/kstars_ui_tests.h @@ -19,49 +19,87 @@ #include #include -#include "kstars.h" - class KStars; -#define KTRY_SHOW_KSTARS() do { \ - KStars * const K = KStars::Instance(); \ - QVERIFY(K != nullptr); \ - QTRY_VERIFY_WITH_TIMEOUT((K)->isGUIReady(), 30000); \ - (K)->raise(); \ - QTRY_VERIFY_WITH_TIMEOUT((K)->isActiveWindow(), 1000); } while(false) - -#define KTRY_ACTION(action_text) do { \ - QAction * const action = KStars::Instance()->actionCollection()->action(action_text); \ - QVERIFY2(action != nullptr, QString("Action '%1' is not registered and cannot be triggered").arg(action_text).toStdString().c_str()); \ - action->trigger(); } while(false) +// All QTest features are macros returning with no error code. +// Therefore, in order to bail out at first failure, tests cannot use functions to run sub-tests and are required to use grouping macros too. +// Tests classes in this folder should attempt to provide new macro shortcuts once their details are properly validated +// This is an example of a test class. +// It inherits from QObject, and defines private slots as test functions class KStarsUiTests : public QObject { Q_OBJECT public: - explicit KStarsUiTests(QObject *parent = nullptr); + explicit KStarsUiTests(QObject *parent = nullptr): QObject(parent) {}; -public: - static struct _InitialConditions - { - QDateTime dateTime; - bool clockRunning; +private slots: - _InitialConditions(): - dateTime(QDate(2020, 01, 01), QTime(23, 0, 0), Qt::UTC), - clockRunning(false) {}; - } - const m_InitialConditions; + /** @brief Members "initTestCase" and "cleanupTestCase" trigger when the framework processes this class. + * Member "initTestCase" is executed before any other declared test. + * Member "cleanupTestCase" is executed after all tests are done. + */ + /** @{ */ + void initTestCase() {}; + void cleanupTestCase() {}; + /** @} */ -private slots: - void initTestCase(); - void cleanupTestCase(); + /** @brief Members "init" and "cleanup" trigger when the framework process one test from this class. + * Member "init" is executed before each declared test. + * Member "cleanup" is executed after each test is done. + */ + /** @{ */ + void init() {}; + void cleanup() {}; + /** @} */ - void init(); - void cleanup(); + /** @brief Tests should be defined as "test" for homogeneity. + * Use QVERIFY and others to validate tests that reply immediately. + * If the application needs to process events while you wait for something, use QTRY_VERIFY_WITH_TIMEOUT. + * If the application needs to process signals while you wait for something, use QTimer::singleShot to run your QVERIFY checks with an arbitrary delay. + * If the application opens dialogs inside signals while you wait for something but you cannot determine the delay, use + * QTimer::singleShot with a lambda, and retrigger the timer until your check can be verified. + */ - void createInstanceTest(); + void testSomethingThatWorks() + { + QVERIFY(QString("this string contains").contains("string")); + }; + + /** @brief Tests that require fixtures can define those in "test_data" as a record list. + * Condition your test with QT_VERSION >= 0x050900 as this is the minimal version that supports this. + * Add data columns to your fixture with QTest::addColum("column-name"). + * In the same order, add data rows to your fixture with QTest::addRow("") << column1 << column2 << ... + * Afterwards, in your test, use QFETCH(, ) to obtain values for one row of your fixture. + * Your test will be run as many times as there are rows, so use initTestCase and cleanupTestCase. + */ + /** @{ */ + void testAnotherThingThatWorks_data() + { + QTest::addColumn ("A"); + QTest::addColumn ("B"); + QTest::addColumn ("C"); + QTest::addRow("1+1=2") << 1 << 1 << 2; + QTest::addRow("1+4=5") << 1 << 4 << 5; + }; - void initialConditionsTest(); - void raiseKStarsTest(); + void testAnotherThingThatWorks() + { + QFETCH(int, A); + QFETCH(int, B); + QFETCH(int, C); + QVERIFY(A+B == C); + }; + /** @} */ + + /** @brief Tests that are built to reproduce a bug should expect failures with QEXPECT_FAIL. + * When the bug is resolved, that test will trigger a failure to verify the fix and may then be updated to remove the expected failure. + * The first argument of QEXPECT_FAIL provides a way to fail on a particular fixture only. + * If the test is not using a fixture or if the failure should trigger for all fixtures, the first argument must be "". + */ + void testSomethingThatFails() + { + QEXPECT_FAIL("", "The next verification will fail, but the test will continue", Continue); + QVERIFY(1+1 == 3); + } }; diff --git a/Tests/kstars_ui/kstars_ui_tests.cpp b/Tests/kstars_ui/kstars_ui_tests.cpp --- a/Tests/kstars_ui/kstars_ui_tests.cpp +++ b/Tests/kstars_ui/kstars_ui_tests.cpp @@ -8,107 +8,37 @@ */ #include "kstars_ui_tests.h" -#include "test_ekos.h" -#include "test_ekos_simulator.h" -#include "test_ekos_focus.h" +#include "kswizard.h" #include "auxiliary/kspaths.h" +#include "test_kstars_startup.h" + #if defined(HAVE_INDI) +#include "test_ekos_wizard.h" +#include "test_ekos.h" +#include "test_ekos_simulator.h" +#include "test_ekos_focus.h" #include "ekos/manager.h" #include "ekos/profileeditor.h" +#include "ekos/profilewizard.h" #endif #include #include #include +#include #include #include #include #include #include +#include +#include #include #include -struct KStarsUiTests::_InitialConditions const KStarsUiTests::m_InitialConditions; - -KStarsUiTests::KStarsUiTests(QObject *parent): QObject(parent) -{ -} - -void KStarsUiTests::initTestCase() -{ -} - -void KStarsUiTests::cleanupTestCase() -{ -} - -void KStarsUiTests::init() -{ - if (KStars::Instance() != nullptr) - KTRY_SHOW_KSTARS(); -} - -void KStarsUiTests::cleanup() -{ - foreach (QDialog * d, KStars::Instance()->findChildren()) - if (d->isVisible()) - d->hide(); -} - -// All QTest features are macros returning with no error code. -// Therefore, in order to bail out at first failure, tests cannot use functions to run sub-tests and are required to use grouping macros too. - -void KStarsUiTests::createInstanceTest() -{ - // Initialize our instance - /*QBENCHMARK_ONCE*/ { - // Create our test instance - KTipDialog::setShowOnStart(false); - KStars::createInstance(true, KStarsUiTests::m_InitialConditions.clockRunning, KStarsUiTests::m_InitialConditions.dateTime.toString()); - QApplication::processEvents(); - } - QVERIFY(KStars::Instance() != nullptr); - - // Initialize our location - GeoLocation * const g = KStars::Instance()->data()->locationNamed("Greenwich"); - QVERIFY(g != nullptr); - KStars::Instance()->data()->setLocation(*g); - QCOMPARE(KStars::Instance()->data()->geo()->lat()->Degrees(), g->lat()->Degrees()); - QCOMPARE(KStars::Instance()->data()->geo()->lng()->Degrees(), g->lng()->Degrees()); -} - -void KStarsUiTests::raiseKStarsTest() -{ - KTRY_SHOW_KSTARS(); -} - -void KStarsUiTests::initialConditionsTest() -{ - QVERIFY(KStars::Instance() != nullptr); - QVERIFY(KStars::Instance()->data() != nullptr); - QVERIFY(KStars::Instance()->data()->clock() != nullptr); - - QCOMPARE(KStars::Instance()->data()->clock()->isActive(), m_InitialConditions.clockRunning); - - QEXPECT_FAIL("", "Initial KStars clock is set from system local time, not geolocation, and is untestable for now.", Continue); - QCOMPARE(KStars::Instance()->data()->clock()->utc().toString(), m_InitialConditions.dateTime.toString()); - - QEXPECT_FAIL("", "Precision of KStars local time conversion to local time does not allow strict millisecond comparison.", Continue); - QCOMPARE(KStars::Instance()->data()->clock()->utc().toLocalTime(), m_InitialConditions.dateTime); - -#if QT_VERSION >= 0x050800 - // However comparison down to nearest second is expected to be OK - QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), m_InitialConditions.dateTime.toSecsSinceEpoch()); - - // Test setting time - KStars::Instance()->data()->clock()->setUTC(KStarsDateTime(m_InitialConditions.dateTime)); - QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), m_InitialConditions.dateTime.toSecsSinceEpoch()); -#endif - } - // We want to launch the application before running our tests // Thus we want to explicitly call QApplication::exec(), and run our tests in parallel of the event loop // We then reimplement QTEST_MAIN(KStarsUiTests); @@ -133,40 +63,53 @@ writableDir.mkdir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)); KCrash::initialize(); - // The instance will be created (and benchmarked) as first test in KStarsUiTests - qDebug("Deferring instance creation to tests."); - // This holds the final result of the test session - int result = 0; + int failure = 0; // Execute tests in sequence, eventually skipping sub-tests based on prior ones QTimer::singleShot(1000, &app, [&] { qDebug("Starting tests..."); - // This creates our KStars instance + // This is a no-op test class for documentation KStarsUiTests * tc = new KStarsUiTests(); - result = QTest::qExec(tc, argc, argv); + failure |= QTest::qExec(tc, argc, argv); + delete tc; + + // This cleans the test user settings, creates our instance and manages the startup wizard + if (!failure) + { + TestKStarsStartup * ti = new TestKStarsStartup(); + failure |= QTest::qExec(ti, argc, argv); + delete ti; + } #if defined(HAVE_INDI) - //if (!result) + if (!failure) + { + TestEkosWizard * ew = new TestEkosWizard(); + failure |= QTest::qExec(ew, argc, argv); + delete ew; + } + + if (!failure) { TestEkos * ek = new TestEkos(); - result |= QTest::qExec(ek, argc, argv); + failure |= QTest::qExec(ek, argc, argv); delete ek; } - //if (!result) + if (!failure) { TestEkosSimulator * ek = new TestEkosSimulator(); - result |= QTest::qExec(ek, argc, argv); + failure |= QTest::qExec(ek, argc, argv); delete ek; } - //if (!result) + if (!failure) { TestEkosFocus * ek = new TestEkosFocus(); - result |= QTest::qExec(ek, argc, argv); + failure |= QTest::qExec(ek, argc, argv); delete ek; } #endif @@ -180,9 +123,14 @@ QTimer::singleShot(5*60*1000, &app, &QCoreApplication::quit); app.exec(); - KStars::Instance()->close(); - delete KStars::Instance(); - return result; + // Clean our instance up if it is still alive + if( KStars::Instance() != nullptr) + { + KStars::Instance()->close(); + delete KStars::Instance(); + } + + return failure; } diff --git a/Tests/kstars_ui/test_ekos.h b/Tests/kstars_ui/test_ekos.h --- a/Tests/kstars_ui/test_ekos.h +++ b/Tests/kstars_ui/test_ekos.h @@ -17,7 +17,11 @@ #if defined(HAVE_INDI) -#include +#include +#include +#include "kstars.h" +#include "ekos/manager.h" +#include "test_kstars_startup.h" #define KVERIFY_EKOS_IS_HIDDEN() do { \ if (Ekos::Manager::Instance() != nullptr) { \ @@ -42,6 +46,12 @@ QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isActiveWindow(), 200); \ QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isVisible(), 200); }} while(false) +#define KHACK_RESET_EKOS_TIME() do { \ + QWARN("HACK HACK HACK: Reset clock to initial conditions when starting Ekos"); \ + if (KStars::Instance() != nullptr) \ + if (KStars::Instance()->data() != nullptr) \ + KStars::Instance()->data()->clock()->setUTC(KStarsDateTime(TestKStarsStartup::m_InitialConditions.dateTime)); } while(false) + class TestEkos: public QObject { Q_OBJECT @@ -60,5 +70,5 @@ void testManipulateProfiles(); }; -#endif +#endif // HAVE_INDI #endif // TEST_EKOS_H diff --git a/Tests/kstars_ui/test_ekos.cpp b/Tests/kstars_ui/test_ekos.cpp --- a/Tests/kstars_ui/test_ekos.cpp +++ b/Tests/kstars_ui/test_ekos.cpp @@ -15,6 +15,7 @@ #if defined(HAVE_INDI) #include "kstars_ui_tests.h" +#include "test_kstars_startup.h" #include "ekos/manager.h" #include "ekos/profileeditor.h" @@ -41,23 +42,24 @@ void TestEkos::initTestCase() { - /* No-op */ } void TestEkos::cleanupTestCase() { - KTRY_CLOSE_EKOS(); } void TestEkos::init() { - KVERIFY_EKOS_IS_HIDDEN(); KTRY_OPEN_EKOS(); KVERIFY_EKOS_IS_OPENED(); } void TestEkos::cleanup() { + foreach (QDialog * d, KStars::Instance()->findChildren()) + if (d->isVisible()) + d->hide(); + KTRY_CLOSE_EKOS(); KVERIFY_EKOS_IS_HIDDEN(); } @@ -112,7 +114,7 @@ #if QT_VERSION >= 0x050800 QEXPECT_FAIL("", "Ekos resets the simulation clock when starting a profile.", Continue); - QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), KStarsUiTests::m_InitialConditions.dateTime.toSecsSinceEpoch()); + QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), TestKStarsStartup::m_InitialConditions.dateTime.toSecsSinceEpoch()); #endif QEXPECT_FAIL("", "Ekos resumes the simulation clock when starting a profile.", Continue); @@ -149,7 +151,7 @@ void TestEkos::testManipulateProfiles() { - // Because we don't want to manager the order of tests, we do the profile manipulation in three steps of the same test + // Because we don't want to manage the order of tests, we do the profile manipulation in three steps of the same test // We use that poor man's shared variable to hold the result of the first (creation) and second (edition) test step. // The ProfileEditor is exec()'d, so test code must be made asynchronous, and QTimer::singleShot is an easy way to do that. // We use two timers, one to run the end-user test, which eventually will close the dialog, and a second one to really close if the test step fails. @@ -161,7 +163,7 @@ // --------- First step: creating the profile // Because the dialog is modal, the remainder of the test is made asynchronous - QTimer::singleShot(200, ekos, [&] + QTimer::singleShot(1000, ekos, [&] { // Find the Profile Editor dialog ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); @@ -334,4 +336,4 @@ QVERIFY(profileCBox->currentText() != testProfileName); } -#endif +#endif // HAVE_INDI diff --git a/Tests/kstars_ui/test_ekos_focus.h b/Tests/kstars_ui/test_ekos_focus.h --- a/Tests/kstars_ui/test_ekos_focus.h +++ b/Tests/kstars_ui/test_ekos_focus.h @@ -16,6 +16,12 @@ #if defined(HAVE_INDI) #include +#include +#include +#include +#include +#include +#include /** @brief Helper to retrieve a gadget in the Focus tab specifically. * @param klass is the class of the gadget to look for. @@ -92,6 +98,7 @@ class TestEkosFocus : public QObject { Q_OBJECT + public: explicit TestEkosFocus(QObject *parent = nullptr); @@ -106,5 +113,5 @@ void testStarDetection(); }; -#endif +#endif // HAVE_INDI #endif // TESTEKOSFOCUS_H diff --git a/Tests/kstars_ui/test_ekos_focus.cpp b/Tests/kstars_ui/test_ekos_focus.cpp --- a/Tests/kstars_ui/test_ekos_focus.cpp +++ b/Tests/kstars_ui/test_ekos_focus.cpp @@ -8,18 +8,17 @@ version 2 of the License, or (at your option) any later version. */ -#include "kstars_ui_tests.h" -#include "test_ekos.h" -#include "test_ekos_simulator.h" + #include "test_ekos_focus.h" -#include "ekos/manager.h" -#include "kstars.h" #if defined(HAVE_INDI) +#include "kstars_ui_tests.h" +#include "test_ekos.h" +#include "test_ekos_simulator.h" + TestEkosFocus::TestEkosFocus(QObject *parent) : QObject(parent) { - } void TestEkosFocus::initTestCase() @@ -30,7 +29,7 @@ KTRY_EKOS_START_SIMULATORS(); // HACK: Reset clock to initial conditions - KStars::Instance()->data()->clock()->setUTC(KStarsDateTime(KStarsUiTests::m_InitialConditions.dateTime)); + KHACK_RESET_EKOS_TIME(); } void TestEkosFocus::cleanupTestCase() @@ -177,4 +176,4 @@ #endif } -#endif +#endif // HAVE_INDI diff --git a/Tests/kstars_ui/test_ekos_simulator.h b/Tests/kstars_ui/test_ekos_simulator.h --- a/Tests/kstars_ui/test_ekos_simulator.h +++ b/Tests/kstars_ui/test_ekos_simulator.h @@ -16,7 +16,12 @@ #ifdef HAVE_INDI #include -#include +#include +#include +#include +#include +#include +#include "ekos/manager.h" #define KTRY_EKOS_SELECT_PROFILE(profile) do { \ QString const p(profile); \ @@ -42,10 +47,9 @@ #define KTRY_EKOS_STOP_SIMULATORS() do { \ KTRY_EKOS_CLICK(disconnectB); \ - QWARN("Intentionally leaving a delay here for BZ398192"); \ - QTest::qWait(5000); \ - KTRY_EKOS_CLICK(processINDIB); \ - QTest::qWait(1000); } while(false) + KTRY_EKOS_GADGET(QPushButton, processINDIB); \ + QTRY_VERIFY_WITH_TIMEOUT(processINDIB->isEnabled(), 5000); \ + KTRY_EKOS_CLICK(processINDIB); } while(false) class TestEkosSimulator : public QObject { diff --git a/Tests/kstars_ui/test_ekos_simulator.cpp b/Tests/kstars_ui/test_ekos_simulator.cpp --- a/Tests/kstars_ui/test_ekos_simulator.cpp +++ b/Tests/kstars_ui/test_ekos_simulator.cpp @@ -12,43 +12,41 @@ #if defined(HAVE_INDI) -#include "kstars_ui_tests.h" #include "test_ekos.h" -#include "ekos/manager.h" #include "kstars.h" #include "ksmessagebox.h" TestEkosSimulator::TestEkosSimulator(QObject *parent) : QObject(parent) { - } void TestEkosSimulator::initTestCase() { - KVERIFY_EKOS_IS_HIDDEN(); KTRY_OPEN_EKOS(); KVERIFY_EKOS_IS_OPENED(); KTRY_EKOS_START_SIMULATORS(); // HACK: Reset clock to initial conditions - KStars::Instance()->data()->clock()->setUTC(KStarsDateTime(KStarsUiTests::m_InitialConditions.dateTime)); + KHACK_RESET_EKOS_TIME(); } void TestEkosSimulator::cleanupTestCase() { + foreach (QDialog * d, KStars::Instance()->findChildren()) + if (d->isVisible()) + d->hide(); + KTRY_EKOS_STOP_SIMULATORS(); KTRY_CLOSE_EKOS(); KVERIFY_EKOS_IS_HIDDEN(); } void TestEkosSimulator::init() { - } void TestEkosSimulator::cleanup() { - } @@ -161,5 +159,4 @@ #endif } - -#endif +#endif // HAVE_INDI diff --git a/Tests/kstars_ui/test_ekos_wizard.h b/Tests/kstars_ui/test_ekos_wizard.h new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_wizard.h @@ -0,0 +1,20 @@ +#ifndef TESTEKOSWIZARD_H +#define TESTEKOSWIZARD_H + +#include + +class TestEkosWizard : public QObject +{ + Q_OBJECT + +public: + explicit TestEkosWizard(QObject *parent = nullptr); + +private slots: + void init(); + void cleanup(); + void testProfileWizard(); + +}; + +#endif // TESTEKOSWIZARD_H diff --git a/Tests/kstars_ui/test_ekos_wizard.cpp b/Tests/kstars_ui/test_ekos_wizard.cpp new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_wizard.cpp @@ -0,0 +1,92 @@ +/* KStars UI tests + Copyright (C) 2017 Csaba Kertesz + Copyright (C) 2020 Eric Dejouhanet + + This application 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. + */ + +#include "kstars_ui_tests.h" + +#if defined(HAVE_INDI) + +#include "Options.h" +#include "test_ekos_wizard.h" +#include "test_ekos.h" +#include "ekos/manager.h" +#include "kswizard.h" +#include "auxiliary/kspaths.h" +#include "ekos/profilewizard.h" + +#include + +TestEkosWizard::TestEkosWizard(QObject *parent) : QObject(parent) +{ +} + +void TestEkosWizard::init() +{ +} + +void TestEkosWizard::cleanup() +{ + foreach (QDialog * d, KStars::Instance()->findChildren()) + if (d->isVisible()) + d->hide(); +} + +void TestEkosWizard::testProfileWizard() +{ + // Update our INDI installation specs + Options::setIndiDriversAreInternal(true); + + // Locate INDI server - this is highly suspicious, but will cover most of the installation cases I suppose + if (QFile("/usr/local/bin/indiserver").exists()) + Options::setIndiServer("/usr/local/bin/indiserver"); + else if (QFile("/usr/bin/indiserver").exists()) + Options::setIndiDriversDir("/usr/bin/indiserver"); + QVERIFY(QDir(Options::indiDriversDir()).exists()); + + // Locate INDI drivers - the XML list of drivers is the generic data path + QFile drivers(KSPaths::locate(QStandardPaths::GenericDataLocation, "indidrivers.xml")); + if (drivers.exists()) + Options::setIndiDriversDir(QFileInfo(drivers).dir().path()); + QVERIFY(QDir(Options::indiDriversDir()).exists()); + + // The Ekos new profile wizard opens when starting Ekos for the first time + bool wizardDone = false; + std::function closeWizard = [&] + { + KStars * const k = KStars::Instance(); + QVERIFY(k != nullptr); + + // Wait for the KStars Wizard to appear + if(k->findChild () == nullptr) + { + QTimer::singleShot(500, KStars::Instance(), closeWizard); + return; + } + ProfileWizard * const w = k->findChild (); + QVERIFY(w != nullptr); + QTRY_VERIFY_WITH_TIMEOUT(w->isVisible(), 1000); + + // Just dismiss the wizard for now + QDialogButtonBox* buttons = w->findChild(); + QVERIFY(nullptr != buttons); + QTest::mouseClick(buttons->button(QDialogButtonBox::Close), Qt::LeftButton); + QTRY_VERIFY_WITH_TIMEOUT(!w->isVisible(), 1000); + wizardDone = true; + }; + QTimer::singleShot(500, Ekos::Manager::Instance(), closeWizard); + + KTRY_OPEN_EKOS(); + KVERIFY_EKOS_IS_OPENED(); + QTRY_VERIFY_WITH_TIMEOUT(wizardDone, 1000); + + KTRY_CLOSE_EKOS(); + KVERIFY_EKOS_IS_HIDDEN(); +} + +#endif // HAVE_INDI diff --git a/Tests/kstars_ui/kstars_ui_tests.h b/Tests/kstars_ui/test_kstars_startup.h copy from Tests/kstars_ui/kstars_ui_tests.h copy to Tests/kstars_ui/test_kstars_startup.h --- a/Tests/kstars_ui/kstars_ui_tests.h +++ b/Tests/kstars_ui/test_kstars_startup.h @@ -1,28 +1,24 @@ /* KStars UI tests Copyright (C) 2017 Csaba Kertesz + Copyright (C) 2020 Eric Dejouhanet This application 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. */ -#pragma once +#ifndef TEST_KSTARS_STARTUP_H +#define TEST_KSTARS_STARTUP_H -#include "config-kstars.h" -#include "kstars.h" -#include "kstarsdata.h" - -#include #include -#include -#include +#include +#include #include +#include #include "kstars.h" -class KStars; - #define KTRY_SHOW_KSTARS() do { \ KStars * const K = KStars::Instance(); \ QVERIFY(K != nullptr); \ @@ -35,11 +31,13 @@ QVERIFY2(action != nullptr, QString("Action '%1' is not registered and cannot be triggered").arg(action_text).toStdString().c_str()); \ action->trigger(); } while(false) -class KStarsUiTests : public QObject + +class TestKStarsStartup : public QObject { Q_OBJECT + public: - explicit KStarsUiTests(QObject *parent = nullptr); + explicit TestKStarsStartup(QObject *parent = nullptr); public: static struct _InitialConditions @@ -57,11 +55,8 @@ void initTestCase(); void cleanupTestCase(); - void init(); - void cleanup(); - void createInstanceTest(); - - void initialConditionsTest(); - void raiseKStarsTest(); + void testInitialConditions(); }; + +#endif // TEST_KSTARS_STARTUP_H diff --git a/Tests/kstars_ui/test_kstars_startup.cpp b/Tests/kstars_ui/test_kstars_startup.cpp new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_kstars_startup.cpp @@ -0,0 +1,151 @@ +/* KStars UI tests + Copyright (C) 2017 Csaba Kertesz + Copyright (C) 2020 Eric Dejouhanet + + This application 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. + */ + +#include "config-kstars.h" + +#include +#include +#include +#include + +#include "Options.h" +#include "kstars.h" +#include "kspaths.h" +#include "kswizard.h" +#include + +#include "kstars_ui_tests.h" +#include "test_kstars_startup.h" + + +struct TestKStarsStartup::_InitialConditions const TestKStarsStartup::m_InitialConditions; + +TestKStarsStartup::TestKStarsStartup(QObject *parent) : QObject(parent) +{ +} + +void TestKStarsStartup::initTestCase() +{ + if (KStars::Instance() != nullptr) + KTRY_SHOW_KSTARS(); +} + +void TestKStarsStartup::cleanupTestCase() +{ + foreach (QDialog * d, KStars::Instance()->findChildren()) + if (d->isVisible()) + d->hide(); +} + +void TestKStarsStartup::createInstanceTest() +{ + // Ensure we are in test mode (user .qttest) + QStandardPaths::setTestModeEnabled(true); + QVERIFY(QStandardPaths::isTestModeEnabled()); + + // Remove the user folder that may eventually exist + QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).removeRecursively(); + QVERIFY(!QDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()); + QVERIFY(QDir().mkpath(KSPaths::writableLocation(QStandardPaths::GenericDataLocation))); + +#if HAVE_INDI + QWARN("INDI driver registry is unexpectedly required before we start the KStars wizard"); + + // Locate INDI drivers like drivermanager.cpp does + Options::setIndiDriversDir( + QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi", QStandardPaths::LocateDirectory)); + QVERIFY(QDir(Options::indiDriversDir()).exists()); + + // Look for the second usual place - developer install - OSX should be there too? + if (QFile("/usr/local/bin/indiserver").exists()) + Options::setIndiServer("/usr/local/bin/indiserver"); + QVERIFY(QFile(Options::indiServer()).exists()); +#endif + + // Prepare to close the wizard pages when the KStars instance will start - we could just use the following to bypass + // Options::setRunStartupWizard(false); + // Remaining in the timer signal waiting for the app to load actually prevents the app from + // loading, so retrigger the timer until the app is ready + volatile bool installWizardDone = false; + std::function closeWizard = [&] + { + QTRY_VERIFY_WITH_TIMEOUT(KStars::Instance() != nullptr, 5000); + KStars * const k = KStars::Instance(); + QVERIFY(k != nullptr); + + // Wait for the KStars Wizard to appear, or retrigger the signal + if(k->findChild () == nullptr) + { + QTimer::singleShot(500, KStars::Instance(), closeWizard); + return; + } + + // Verify it is a KSWizard that appeared + KSWizard * const w = k->findChild (); + QVERIFY(w != nullptr); + QTRY_VERIFY_WITH_TIMEOUT(w->isVisible(), 1000); + + // Wait for the New Installation Wizard inside that KSWizard + QTRY_VERIFY_WITH_TIMEOUT(w->findChild ("WizWelcome") != nullptr, 1000); + QWidget * ww = KStars::Instance()->findChild ("WizWelcome"); + QTRY_VERIFY_WITH_TIMEOUT(ww->isVisible(), 1000); + + // We could shift to all pages one after the other, but the Next button is difficult to locate, so just dismiss the wizard lazily + QDialogButtonBox* buttons = w->findChild(); + QVERIFY(nullptr != buttons); + QTest::mouseClick(buttons->button(QDialogButtonBox::Cancel), Qt::LeftButton); + + installWizardDone = true; + }; + QTimer::singleShot(500, KStars::Instance(), closeWizard); + + // Initialize our instance and wait for the test to finish + KTipDialog::setShowOnStart(false); + KStars::createInstance(true, m_InitialConditions.clockRunning, m_InitialConditions.dateTime.toString()); + QVERIFY(KStars::Instance() != nullptr); + QTRY_VERIFY_WITH_TIMEOUT(installWizardDone, 10000); + + // With our instance created, initialize our location + // FIXME: do this via UI in the Startup Wizard + KStarsData * const d = KStars::Instance()->data(); + QVERIFY(d != nullptr); + GeoLocation * const g = d->locationNamed("Greenwich"); + QVERIFY(g != nullptr); + d->setLocation(*g); + + // Verify our location is properly selected + QCOMPARE(d->geo()->lat()->Degrees(), g->lat()->Degrees()); + QCOMPARE(d->geo()->lng()->Degrees(), g->lng()->Degrees()); +} + +void TestKStarsStartup::testInitialConditions() +{ + QVERIFY(KStars::Instance() != nullptr); + QVERIFY(KStars::Instance()->data() != nullptr); + QVERIFY(KStars::Instance()->data()->clock() != nullptr); + + QCOMPARE(KStars::Instance()->data()->clock()->isActive(), m_InitialConditions.clockRunning); + + QEXPECT_FAIL("", "Initial KStars clock is set from system local time, not geolocation, and is untestable for now.", Continue); + QCOMPARE(KStars::Instance()->data()->clock()->utc().toString(), m_InitialConditions.dateTime.toString()); + + QEXPECT_FAIL("", "Precision of KStars local time conversion to local time does not allow strict millisecond comparison.", Continue); + QCOMPARE(KStars::Instance()->data()->clock()->utc().toLocalTime(), m_InitialConditions.dateTime); + +#if QT_VERSION >= 0x050800 + // However comparison down to nearest second is expected to be OK + QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), m_InitialConditions.dateTime.toSecsSinceEpoch()); + + // Test setting time + KStars::Instance()->data()->clock()->setUTC(KStarsDateTime(m_InitialConditions.dateTime)); + QCOMPARE(llround(KStars::Instance()->data()->clock()->utc().toLocalTime().toMSecsSinceEpoch()/1000.0), m_InitialConditions.dateTime.toSecsSinceEpoch()); +#endif +} + diff --git a/kstars/kstarsinit.cpp b/kstars/kstarsinit.cpp --- a/kstars/kstarsinit.cpp +++ b/kstars/kstarsinit.cpp @@ -933,9 +933,8 @@ initStatusBar(); initActions(); - setupGUI(StandardWindowOptions(Default & ~Create)); - - createGUI("kstarsui.rc"); + // Provide resource file explicitely for UI tests to display resources properly + setupGUI(StandardWindowOptions(Default), ":/kxmlgui5/kstars/kstarsui.rc"); //get focus of keyboard and mouse actions (for example zoom in with +) map()->QWidget::setFocus();