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,7 +1,8 @@ SET(KSTARS_UI_TESTS_SRC + kstars_ui_tests.cpp test_ekos.cpp - kstars_ui_tests.cpp) + test_ekos_simulator.cpp) include_directories(${CFITSIO_INCLUDE_DIR}) 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 @@ -15,51 +15,51 @@ #include #include #include -#include +#include #include "kstars.h" class KStars; -#define KTRY_SHOW_KSTARS(K) do { \ - QTRY_VERIFY_WITH_TIMEOUT((K)->isGUIReady(), 1000); \ +#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) + class KStarsUiTests : public QObject { Q_OBJECT +public: + explicit KStarsUiTests(QObject *parent = nullptr); public: - KStarsUiTests(); - ~KStarsUiTests() override = default; + static struct _InitialConditions + { + QDateTime dateTime; + bool clockRunning; -private: - KStars* K {nullptr}; + _InitialConditions(): + dateTime(QDate(2020, 01, 01), QTime(23, 0, 0), Qt::UTC), + clockRunning(false) {}; + } + const m_InitialConditions; private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); - void basicTest(); - void warnTest(); - void raiseKStarsTest(); - -#if defined(HAVE_INDI) -private: - QString testProfileName; - void initEkos(); - void cleanupEkos(); + void createInstanceTest(); -private slots: - // UI - void openEkosTest(); - - // Profiles - void manipulateEkosProfiles(); - void testdriveSimulatorProfile(); -#endif + void initialConditionsTest(); + void raiseKStarsTest(); }; 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,6 +8,8 @@ */ #include "kstars_ui_tests.h" +#include "test_ekos.h" +#include "test_ekos_simulator.h" #include "auxiliary/kspaths.h" #if defined(HAVE_INDI) @@ -23,71 +25,82 @@ #include #include #include +#include +#include #include #include -KStarsUiTests::KStarsUiTests() -{ - srand((unsigned int)time(nullptr)); - // Create writable data dir if it does not exist - QDir writableDir; +struct KStarsUiTests::_InitialConditions const KStarsUiTests::m_InitialConditions; - writableDir.mkdir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)); - KCrash::initialize(); +KStarsUiTests::KStarsUiTests(QObject *parent): QObject(parent) +{ } void KStarsUiTests::initTestCase() { - KTipDialog::setShowOnStart(false); - K = KStars::createInstance(true); } void KStarsUiTests::cleanupTestCase() { - K->close(); - delete K; - K = nullptr; } void KStarsUiTests::init() { - KStars* const K = KStars::Instance(); - QVERIFY(K != nullptr); - KTRY_SHOW_KSTARS(K); - -#if defined(HAVE_INDI) - initEkos(); -#endif + if (KStars::Instance() != nullptr) + KTRY_SHOW_KSTARS(); } void KStarsUiTests::cleanup() { foreach (QDialog * d, KStars::Instance()->findChildren()) if (d->isVisible()) d->hide(); - -#if defined(HAVE_INDI) - cleanupEkos(); -#endif } // 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::basicTest() +void KStarsUiTests::createInstanceTest() { - QVERIFY(true); + // 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::warnTest() +void KStarsUiTests::raiseKStarsTest() { - QWARN("This is a test expected to print a warning message."); + KTRY_SHOW_KSTARS(); } -void KStarsUiTests::raiseKStarsTest() +void KStarsUiTests::initialConditionsTest() { - KTRY_SHOW_KSTARS(K); + 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); + + // 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()); } // We want to launch the application before running our tests @@ -101,14 +114,60 @@ int main(int argc, char *argv[]) { + // Create our test application QApplication app(argc, argv); app.setAttribute(Qt::AA_Use96Dpi, true); QTEST_ADD_GPU_BLACKLIST_SUPPORT - QApplication::processEvents(); - KStarsUiTests * tc = new KStarsUiTests(); QTEST_SET_MAIN_SOURCE_PATH - QTimer::singleShot(1000, &app, [=]() { return QTest::qExec(tc, argc, argv); }); - QTimer::singleShot(30000, &app, &QCoreApplication::quit); + QApplication::processEvents(); + + // Prepare our configuration + srand((unsigned int)time(nullptr)); + QDir writableDir; + 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; + + // Execute tests in sequence, eventually skipping sub-tests based on prior ones + QTimer::singleShot(1000, &app, [&] + { + qDebug("Starting tests..."); + + // This creates our KStars instance + KStarsUiTests * tc = new KStarsUiTests(); + result = QTest::qExec(tc, argc, argv); + +#if defined(HAVE_INDI) + if (!result) + { + TestEkos * ek = new TestEkos(); + result |= QTest::qExec(ek, argc, argv); + } + + if (!result) + { + TestEkosSimulator * eks = new TestEkosSimulator(); + result |= QTest::qExec(eks, argc, argv); + } +#endif + + // Done testing, successful or not + qDebug("Tests are done."); + app.quit(); + }); + + // Limit execution duration + QTimer::singleShot(5*60*1000, &app, &QCoreApplication::quit); + app.exec(); + KStars::Instance()->close(); + delete KStars::Instance(); + + return result; } diff --git a/Tests/kstars_ui/test_ekos.h b/Tests/kstars_ui/test_ekos.h new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_ekos.h @@ -0,0 +1,64 @@ +/* KStars UI tests + Copyright (C) 2018, 2020 + Csaba Kertesz + Jasem Mutlaq + 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. + */ + +#ifndef TEST_EKOS_H +#define TEST_EKOS_H + +#include "config-kstars.h" + +#if defined(HAVE_INDI) + +#include + +#define KVERIFY_EKOS_IS_HIDDEN() do { \ + if (Ekos::Manager::Instance() != nullptr) { \ + QVERIFY(!Ekos::Manager::Instance()->isVisible()); \ + QVERIFY(!Ekos::Manager::Instance()->isActiveWindow()); }} while(false) + +#define KVERIFY_EKOS_IS_OPENED() do { \ + QVERIFY(Ekos::Manager::Instance() != nullptr); \ + QVERIFY(Ekos::Manager::Instance()->isVisible()); \ + QVERIFY(Ekos::Manager::Instance()->isActiveWindow()); } while(false) + +#define KTRY_OPEN_EKOS() do { \ + if (Ekos::Manager::Instance() == nullptr || !Ekos::Manager::Instance()->isVisible()) { \ + KTRY_ACTION("show_ekos"); \ + QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance() != nullptr, 200); \ + QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->isVisible(), 200); \ + QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->isActiveWindow(), 200); }} while(false) + +#define KTRY_CLOSE_EKOS() do { \ + if (Ekos::Manager::Instance() != nullptr && Ekos::Manager::Instance()->isVisible()) { \ + KTRY_ACTION("show_ekos"); \ + QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isActiveWindow(), 200); \ + QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isVisible(), 200); }} while(false) + +class TestEkos: public QObject +{ + Q_OBJECT +public: + explicit TestEkos(QObject *parent = nullptr); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void init(); + void cleanup(); + + void testOpenClose(); + void testSimulatorProfile(); + void testManipulateProfiles(); +}; + +#endif +#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 @@ -1,5 +1,5 @@ /* KStars UI tests - Copyright (C) 218, 2020 + Copyright (C) 2018, 2020 Csaba Kertesz Jasem Mutlaq Eric Dejouhanet @@ -10,7 +10,7 @@ version 2 of the License, or (at your option) any later version. */ -#include "config-kstars.h" +#include "test_ekos.h" #if defined(HAVE_INDI) @@ -34,76 +34,51 @@ #include #include -#define KTRY_ACTION(action_text) do { \ - QAction * const action = KStars::Instance()->actionCollection()->action(action_text); \ - QVERIFY(action != nullptr); \ - action->trigger(); } while(false) - -#define KVERIFY_EKOS_IS_HIDDEN() do { \ - if (Ekos::Manager::Instance() != nullptr) { \ - QVERIFY(!Ekos::Manager::Instance()->isVisible()); \ - QVERIFY(!Ekos::Manager::Instance()->isActiveWindow()); }} while(false) - -#define KVERIFY_EKOS_IS_OPENED() do { \ - QVERIFY(Ekos::Manager::Instance() != nullptr); \ - QVERIFY(Ekos::Manager::Instance()->isVisible()); \ - QVERIFY(Ekos::Manager::Instance()->isActiveWindow()); } while(false) - -#define KTRY_OPEN_EKOS() do { \ - if (Ekos::Manager::Instance() == nullptr || !Ekos::Manager::Instance()->isVisible()) { \ - KTRY_ACTION("show_ekos"); \ - QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance() != nullptr, 200); \ - QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->isVisible(), 200); \ - QTRY_VERIFY_WITH_TIMEOUT(Ekos::Manager::Instance()->isActiveWindow(), 200); }} while(false) - -#define KTRY_CLOSE_EKOS() do { \ - if (Ekos::Manager::Instance() != nullptr && Ekos::Manager::Instance()->isVisible()) { \ - KTRY_ACTION("show_ekos"); \ - QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isActiveWindow(), 200); \ - QTRY_VERIFY_WITH_TIMEOUT(!Ekos::Manager::Instance()->isVisible(), 200); }} while(false) - -void KStarsUiTests::initEkos() +TestEkos::TestEkos(QObject *parent): QObject(parent) +{ + +} + +void TestEkos::initTestCase() { /* No-op */ } -void KStarsUiTests::cleanupEkos() +void TestEkos::cleanupTestCase() { KTRY_CLOSE_EKOS(); } -void KStarsUiTests::openEkosTest() +void TestEkos::init() { KVERIFY_EKOS_IS_HIDDEN(); KTRY_OPEN_EKOS(); KVERIFY_EKOS_IS_OPENED(); - KTRY_CLOSE_EKOS(); - KVERIFY_EKOS_IS_HIDDEN(); } -void KStarsUiTests::testdriveSimulatorProfile() +void TestEkos::cleanup() { + KTRY_CLOSE_EKOS(); KVERIFY_EKOS_IS_HIDDEN(); - KTRY_OPEN_EKOS(); - KVERIFY_EKOS_IS_OPENED(); +} - // Because we don't want to manager 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. - bool testIsSuccessful = false; +void TestEkos::testOpenClose() +{ + /* No-op, we just use init+cleanup */ +} +void TestEkos::testSimulatorProfile() +{ Ekos::Manager * const ekos = Ekos::Manager::Instance(); // --------- First step: selecting the Simulators profile - QString const testProfileName("Simulators"); - // Verify that the test profile exists, and select it - QComboBox* profileCBox = ekos->findChild("profileCombo"); + QString const p("Simulators"); + QComboBox* profileCBox = Ekos::Manager::Instance()->findChild("profileCombo"); QVERIFY(profileCBox != nullptr); - profileCBox->setCurrentText(testProfileName); - QCOMPARE(profileCBox->currentText(), testProfileName); + profileCBox->setCurrentText(p); + QTRY_COMPARE(profileCBox->currentText(), p); // --------- Second step: starting Ekos with the Simulators profile @@ -121,7 +96,7 @@ // The INDI property pages automatically raised on top, but as we got a handle to the button, we continue to test as is // Wait until Ekos gives feedback on the INDI client startup - button changes to symbol "stop" - QTRY_VERIFY_WITH_TIMEOUT(!buttonReadyToStop.compare(startEkos->icon().name()), 5000); + QTRY_VERIFY_WITH_TIMEOUT(!buttonReadyToStop.compare(startEkos->icon().name()), 7000); // It might be that our Simulators profile is not auto-connecting and we should do that manually // We assume that by default it's not the case @@ -135,54 +110,56 @@ QVERIFY(connectDevices != nullptr); QVERIFY(!connectDevices->isEnabled()); + 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()); + + QEXPECT_FAIL("", "Ekos resumes the simulation clock when starting a profile.", Continue); + QVERIFY(!KStars::Instance()->data()->clock()->isActive()); + // --------- Fourth step: waiting for Ekos to finish stopping // Start button that became a stop button is now disabled - we need to disconnect devices first QVERIFY(!startEkos->isEnabled()); - // Verify the device disconnection button is available - QPushButton * disconnectDevices = ekos->findChild("disconnectB"); - QVERIFY(disconnectDevices != nullptr); - - // Disconnect INDI devices - QTimer::singleShot(200, ekos, [&]() + // Verify the device disconnection button is available, disconnect devices + QPushButton * const b = ekos->findChild("disconnectB"); + QVERIFY(b != nullptr); + QVERIFY(b->isEnabled()); + QTimer::singleShot(200, Ekos::Manager::Instance(), [&] { - QTest::mouseClick(disconnectDevices, Qt::LeftButton); + QTest::mouseClick(b, Qt::LeftButton); }); QWARN("Intentionally leaving a delay here for BZ398192"); QTest::qWait(5000); // --------- Fifth step: waiting for Ekos to finish stopping // Start button that became a stop button has to be available - QTRY_VERIFY_WITH_TIMEOUT(startEkos->isEnabled(), 5000); + QTRY_VERIFY_WITH_TIMEOUT(startEkos->isEnabled(), 7000); // Hang INDI client up - QTimer::singleShot(200, ekos, [&]() + QTimer::singleShot(200, ekos, [&] { QTest::mouseClick(startEkos, Qt::LeftButton); }); - QTRY_VERIFY_WITH_TIMEOUT(!buttonReadyToStart.compare(startEkos->icon().name()), 5000); + QTRY_VERIFY_WITH_TIMEOUT(!buttonReadyToStart.compare(startEkos->icon().name()), 7000); } -void KStarsUiTests::manipulateEkosProfiles() +void TestEkos::testManipulateProfiles() { - KVERIFY_EKOS_IS_HIDDEN(); - KTRY_OPEN_EKOS(); - KVERIFY_EKOS_IS_OPENED(); - // Because we don't want to manager 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. bool testIsSuccessful = false; Ekos::Manager * const ekos = Ekos::Manager::Instance(); + QString testProfileName = QString("testUI%1").arg(rand() % 100000); // FIXME: Move this to fixtures // --------- First step: creating the profile // Because the dialog is modal, the remainder of the test is made asynchronous - QTimer::singleShot(200, ekos, [&]() + QTimer::singleShot(200, ekos, [&] { // Find the Profile Editor dialog ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); @@ -198,7 +175,6 @@ // Create a test profile QLineEdit * const profileNameLE = profileEditor->findChild("profileIN"); QVERIFY(nullptr != profileNameLE); - testProfileName = QString("testUI%1").arg(rand() % 100000); // FIXME: Move this to fixtures profileNameLE->setText(testProfileName); QCOMPARE(profileNameLE->text(), testProfileName); @@ -237,7 +213,7 @@ QTimer * closeDialog = new QTimer(this); closeDialog->setSingleShot(true); closeDialog->setInterval(1000); - ekos->connect(closeDialog, &QTimer::timeout, [&]() + ekos->connect(closeDialog, &QTimer::timeout, [&] { ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); if (profileEditor != nullptr) @@ -266,7 +242,7 @@ QCOMPARE(profileCBox->currentText(), testProfileName); // Because the dialog is modal, the remainder of the test is made asynchronous - QTimer::singleShot(200, ekos, [&]() + QTimer::singleShot(200, ekos, [&] { // Find the Profile Editor dialog ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); @@ -297,7 +273,7 @@ closeDialog = new QTimer(this); closeDialog->setSingleShot(true); closeDialog->setInterval(1000); - ekos->connect(closeDialog, &QTimer::timeout, [&]() + ekos->connect(closeDialog, &QTimer::timeout, [&] { ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); if (profileEditor != nullptr) @@ -326,7 +302,7 @@ // --------- Third step: deleting the profile // The yes/no modal dialog is not really used as a blocking question, but let's keep the remainder of the test is made asynchronous - QTimer::singleShot(200, ekos, [&]() + QTimer::singleShot(200, ekos, [&] { // This trick is from https://stackoverflow.com/questions/38596785 QTRY_VERIFY_WITH_TIMEOUT(QApplication::activeModalWidget() != nullptr, 1000); diff --git a/Tests/kstars_ui/test_ekos_simulator.h b/Tests/kstars_ui/test_ekos_simulator.h new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_simulator.h @@ -0,0 +1,65 @@ +/* KStars UI tests + 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. + */ + +#ifndef TESTEKOSSIMULATOR_H +#define TESTEKOSSIMULATOR_H + +#include "config-kstars.h" + +#ifdef HAVE_INDI + +#include +#include + +#define KTRY_EKOS_SELECT_PROFILE(profile) do { \ + QString const p(profile); \ + QComboBox* profileCBox = Ekos::Manager::Instance()->findChild("profileCombo"); \ + QVERIFY(profileCBox != nullptr); \ + profileCBox->setCurrentText(p); \ + QTRY_COMPARE(profileCBox->currentText(), p); } while(false) + +#define KTRY_EKOS_CLICK(button) do { \ + QPushButton * const b = Ekos::Manager::Instance()->findChild(button); \ + QVERIFY2(b != nullptr, QString("QPushButton '%1' does not exist and cannot be clicked").arg(button).toStdString().c_str()); \ + QVERIFY2(b->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(button).toStdString().c_str()); \ + QTimer::singleShot(200, Ekos::Manager::Instance(), [&]() { QTest::mouseClick(b, Qt::LeftButton); }); } while(false) + +#define KTRY_EKOS_START_SIMULATORS() do { \ + KTRY_EKOS_SELECT_PROFILE("Simulators"); \ + KTRY_EKOS_CLICK("processINDIB"); \ + QWARN("HACK HACK HACK adding delay here for devices to connect"); \ + QTest::qWait(5000); } while(false) + +#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) + +class TestEkosSimulator : public QObject +{ + Q_OBJECT +public: + explicit TestEkosSimulator(QObject *parent = nullptr); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void init(); + void cleanup(); + + void testMountSlew_data(); + void testMountSlew(); +}; + +#endif +#endif // TESTEKOSSIMULATOR_H diff --git a/Tests/kstars_ui/test_ekos_simulator.cpp b/Tests/kstars_ui/test_ekos_simulator.cpp new file mode 100644 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_simulator.cpp @@ -0,0 +1,151 @@ +/* KStars UI tests + 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 "test_ekos_simulator.h" + +#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(); + +} + +void TestEkosSimulator::cleanupTestCase() +{ + KTRY_EKOS_STOP_SIMULATORS(); + KTRY_CLOSE_EKOS(); + KVERIFY_EKOS_IS_HIDDEN(); +} + +void TestEkosSimulator::init() +{ + +} + +void TestEkosSimulator::cleanup() +{ + +} + + +void TestEkosSimulator::testMountSlew_data() +{ + QTest::addColumn("NAME"); + QTest::addColumn("RA"); + QTest::addColumn("DEC"); + + // Altitude computation taken from SchedulerJob::findAltitude + GeoLocation * const geo = KStarsData::Instance()->geo(); + KStarsDateTime const now(KStarsData::Instance()->lt()); + KSNumbers const numbers(now.djd()); + CachingDms const LST = geo->GSTtoLST(geo->LTtoUT(now).gst()); + + // Build a list of Messier objects, 5 degree over the horizon + for (int i = 1; i < 103; i++) + { + QString name = QString("M %1").arg(i); + SkyObject const * const so = KStars::Instance()->data()->objectNamed(name); + if (so != nullptr) + { + SkyObject o(*so); + o.updateCoordsNow(&numbers); + o.EquatorialToHorizontal(&LST, geo->lat()); + if (5.0 < so->alt().Degrees()) + QTest::addRow(name.toStdString().c_str()) + << name + << so->ra().toHMSString() + << so->dec().toDMSString(); + } + } +} + +void TestEkosSimulator::testMountSlew() +{ + Ekos::Manager * const ekos = Ekos::Manager::Instance(); + + QFETCH(QString, NAME); + QFETCH(QString, RA); + QFETCH(QString, DEC); + qDebug(QString("Test slewing to '%1' RA '%2' DEC '%3'").arg(NAME).arg(RA).arg(DEC).toStdString().c_str()); + +#if 0 + // In the mount tab, open the mount control + KTRY_EKOS_CLICK("mountToolBoxB"); + QTextObject * raInput = Ekos::Manager::Instance()->findChild("targetRATextObject"); + QVERIFY(raInput != nullptr); + raInput->setProperty("text", QVariant("07h 37m 30s")); + QTest::qWait(1000); + QTextObject * deInput = Ekos::Manager::Instance()->findChild("targetDETextObject"); + QVERIFY(deInput != nullptr); + deInput->setProperty("text", QVariant("-14° 31' 50")); + QTest::qWait(1000); + + // Too bad, not accessible... + QPushButton * gotoButton = Ekos::Manager::Instance()->findChild(""); + + QTest::qWait(5000); + KTRY_EKOS_CLICK("mountToolBoxB"); +#else + QVERIFY(ekos->mountModule()->abort()); + // Catch the unexpected "under horizon" and the "sun is close" dialogs + // This will not catch the altitude security interval + bool under_horizon_or_close_to_sun = false; + QTimer::singleShot(1000, [&] + { + QDialog * const dialog = qobject_cast (QApplication::activeModalWidget()); + if(dialog != nullptr) + { + under_horizon_or_close_to_sun = true; + emit dialog->reject(); + } + }); + bool const slew_result = ekos->mountModule()->slew(RA, DEC); + if (under_horizon_or_close_to_sun) + QEXPECT_FAIL(NAME.toStdString().c_str(), QString("Slew target '%1' is expected to be over the horizon during night time.").arg(NAME).toStdString().c_str(), Abort); + QVERIFY(slew_result); +#endif + + // DEC slews are precise at plus/minus one arcsecond - expected or not? + auto clampRA = [](QString v) { return CachingDms(v, false).arcsec(); }; + auto clampDE = [](QString v) { return CachingDms(v, true).arcsec(); }; + + QLineEdit * raOut = ekos->findChild("raOUT"); + QVERIFY(raOut != nullptr); + QTRY_VERIFY_WITH_TIMEOUT(abs(clampRA(RA) - clampRA(raOut->text())) < 2, 15000); + QTest::qWait(100); + if (clampRA(RA) != clampRA(raOut->text())) + QWARN(QString("Target '%1' slewed to with offset RA %2").arg(NAME).arg(RA).toStdString().c_str()); + + QLineEdit * deOut = Ekos::Manager::Instance()->findChild("decOUT"); + QVERIFY(deOut != nullptr); + QTRY_VERIFY_WITH_TIMEOUT(abs(clampDE(DEC) - clampDE(deOut->text())) < 2, 15000); + QTest::qWait(100); + if (clampRA(DEC) != clampRA(deOut->text())) + QWARN(QString("Target '%1' slewed to with coordinate offset DEC %2").arg(NAME).arg(DEC).toStdString().c_str()); + + QVERIFY(Ekos::Manager::Instance()->mountModule()->abort()); +} + +#endif