diff --git a/Tests/kstars_ui/CMakeLists.txt b/Tests/kstars_ui/CMakeLists.txt index 9f0a7b762fab3193d2b9bafe0f9888ea51d70421..5cfabb5bf4977be6a42f2dbf480371a0e3715e9e 100644 --- a/Tests/kstars_ui/CMakeLists.txt +++ b/Tests/kstars_ui/CMakeLists.txt @@ -42,7 +42,11 @@ ADD_EXECUTABLE(test_ekos_capture ${KSTARS_UI_EKOS_SRC} test_ekos_capture.cpp) TARGET_LINK_LIBRARIES(test_ekos_capture ${KSTARS_UI_EKOS_LIBS}) ADD_TEST(NAME TestEkosCapture COMMAND test_ekos_capture) -ADD_EXECUTABLE(test_ekos_meridianflip ${KSTARS_UI_EKOS_SRC} test_ekos_meridianflip_base.cpp test_ekos_meridianflip.cpp) +ADD_EXECUTABLE(test_ekos_capture_count ${KSTARS_UI_EKOS_SRC} test_ekos_capture_helper.cpp test_ekos_capture_count.cpp) +TARGET_LINK_LIBRARIES(test_ekos_capture_count ${KSTARS_UI_EKOS_LIBS}) +ADD_TEST(NAME TestEkosCaptureCount COMMAND test_ekos_capture_count) + +ADD_EXECUTABLE(test_ekos_meridianflip ${KSTARS_UI_EKOS_SRC} test_ekos_capture_helper.cpp test_ekos_meridianflip_base.cpp test_ekos_meridianflip.cpp) TARGET_LINK_LIBRARIES(test_ekos_meridianflip ${KSTARS_UI_EKOS_LIBS}) # excluded to avoid CI timelimit failure # ADD_TEST(NAME TestEkosMeridianFlip COMMAND test_ekos_meridianflip) @@ -51,7 +55,7 @@ ADD_CUSTOM_COMMAND( TARGET test_ekos_meridianflip POST_BUILD ${CMAKE_CURRENT_SOURCE_DIR}/phd2_Simulators_mf.PHDGuidingV2 ${CMAKE_CURRENT_BINARY_DIR}/.PHDGuidingV2_mf) -ADD_EXECUTABLE(test_ekos_meridianflip_specials ${KSTARS_UI_EKOS_SRC} test_ekos_meridianflip_base.cpp test_ekos_meridianflip_specials.cpp) +ADD_EXECUTABLE(test_ekos_meridianflip_specials ${KSTARS_UI_EKOS_SRC} test_ekos_capture_helper.cpp test_ekos_meridianflip_base.cpp test_ekos_meridianflip_specials.cpp) TARGET_LINK_LIBRARIES(test_ekos_meridianflip_specials ${KSTARS_UI_EKOS_LIBS}) # excluded to avoid CI timelimit failure # ADD_TEST(NAME TestEkosMeridianFlipSpecials COMMAND test_ekos_meridianflip_specials) diff --git a/Tests/kstars_ui/test_ekos_capture_count.cpp b/Tests/kstars_ui/test_ekos_capture_count.cpp new file mode 100644 index 0000000000000000000000000000000000000000..80fe376f7d124703d3a8deed240d4413a6971f11 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_capture_count.cpp @@ -0,0 +1,473 @@ +/* + KStars UI tests for verifying correct counting of the capture module + + Copyright (C) 2020 + Wolfgang Reissenberger + + 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_capture_count.h" + +#include "kstars_ui_tests.h" +#include "test_ekos.h" +#include "Options.h" + +#include "test_ekos_capture_helper.h" + +TestEkosCaptureCount::TestEkosCaptureCount(QObject *parent) : QObject(parent) { + m_CaptureHelper = new TestEkosCaptureHelper(); +} + + +void TestEkosCaptureCount::testCaptureWithCaptureFramesMap() +{ + // clean up capture module + Ekos::Manager::Instance()->captureModule()->clearSequenceQueue(); + KTRY_GADGET(Ekos::Manager::Instance()->captureModule(), QTableWidget, queueTable); + QTRY_VERIFY_WITH_TIMEOUT(queueTable->rowCount() == 0, 2000); + + // setup capture sequence, fill captured frames map and set expectations + QVERIFY(prepareCapture()); + + // verify if at least one capture is expected + QVERIFY(executeCapturing()); +} + +void TestEkosCaptureCount::cleanupScheduler() +{ + Ekos::Manager::Instance()->schedulerModule()->stop(); + QTest::qWait(5000); + // remove jobs + Ekos::Manager::Instance()->schedulerModule()->removeAllJobs(); +} + +void TestEkosCaptureCount::testSchedulerCapture() +{ + // prepare captured frames + QVERIFY(prepareScheduledCapture()); + + // start scheduler job + KTRY_CLICK(Ekos::Manager::Instance()->schedulerModule(), startB); + // expect a idle signal at the end + expectedSchedulerStates.enqueue(Ekos::SCHEDULER_IDLE); + + // ensure that the scheduler has started capturing + m_CaptureHelper->expectedCaptureStates.enqueue(Ekos::CAPTURE_CAPTURING); + QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->expectedCaptureStates.size() == 0, 10000); + + // wait for finish capturing + KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(expectedSchedulerStates, 120000); + + // verify whether all frames are captured as expected + QVERIFY2(checkCapturedFrames(), "Capturing did not produce the expected amount of frames."); +} + +/* ********************************************************************************* + * + * Test data + * + * ********************************************************************************* */ + +void TestEkosCaptureCount::testSchedulerCapture_data() +{ + prepareTestData(1.0, "Red:2,Green:2,Blue:2", "Red:2,Green:1,Blue:2", "Green:1"); + prepareTestData(1.0, "Red:1,Red:1,Green:1,Green:1,Blue:1,Blue:1", "Red:2,Green:1,Blue:2", "Green:1"); + prepareTestData(1.0, "Red:1,Green:1,Blue:1,Red:1,Green:1,Blue:1", "Red:2,Green:1,Blue:2", "Green:1"); + prepareTestData(1.0, "Red:1,Green:1,Blue:1", "Red:3,Green:1,Blue:3", "Green:2", 3); + prepareTestData(1.0, "Luminance:3,Red:1,Green:1,Blue:1,Luminance:2", "Luminance:5,Green:1,Blue:1", "Red:1"); + prepareTestData(1.0, "Luminance:3,Red:1,Green:1,Blue:1,Luminance:2", "", "Luminance:10,Red:2,Green:2,Blue:2", 2); + prepareTestData(1.0, "Luminance:3,Red:1,Green:1,Blue:1,Luminance:2", "Luminance:15,Red:1,Green:2,Blue:2", "Red:1", 2); + prepareTestData(1.0, "Luminance:3,Red:1,Green:1,Blue:1,Luminance:2", "Luminance:15,Red:2,Green:3,Blue:3", "Red:1", 3); + prepareTestData(1.0, "Luminance:3,Red:1,Green:1,Blue:1,Luminance:2", "Luminance:15,Red:3,Green:3,Blue:2", "Blue:1", 3); +} + +void TestEkosCaptureCount::testCaptureWithCaptureFramesMap_data() +{ + // use the same test set + testSchedulerCapture_data(); +} + +/* ********************************************************************************* + * + * Test infrastructure + * + * ********************************************************************************* */ + +void TestEkosCaptureCount::initTestCase() +{ + KVERIFY_EKOS_IS_HIDDEN(); + KTRY_OPEN_EKOS(); + KVERIFY_EKOS_IS_OPENED(); + // start the profile + QVERIFY(startEkosProfile()); + m_CaptureHelper->initTestCase(); + QStandardPaths::setTestModeEnabled(true); + QFileInfo test_dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation), "test"); + destination = new QTemporaryDir(test_dir.absolutePath()); + QVERIFY(destination->isValid()); + QVERIFY(destination->autoRemove()); + // do not show images + Options::setUseFITSViewer(false); + // disable twilight warning + KMessageBox::saveDontShowAgainYesNo("astronomical_twilight_warning", KMessageBox::ButtonCode::No); +} + +void TestEkosCaptureCount::cleanupTestCase() +{ + m_CaptureHelper->cleanupTestCase(); + QVERIFY(shutdownEkosProfile()); + KTRY_CLOSE_EKOS(); + KVERIFY_EKOS_IS_HIDDEN(); + + // remove destination directory + destination->remove(); + delete destination; +} + +void TestEkosCaptureCount::init() +{ + connect(Ekos::Manager::Instance()->captureModule(), &Ekos::Capture::captureComplete, this, &TestEkosCaptureCount::captureComplete); + connect(Ekos::Manager::Instance()->schedulerModule(), &Ekos::Scheduler::newStatus, this, &TestEkosCaptureCount::schedulerStateChanged); + QStandardPaths::setTestModeEnabled(true); + // clear image directory + QVERIFY(getImageLocation()->removeRecursively()); +} + +void TestEkosCaptureCount::cleanup() +{ + QVERIFY(m_CaptureHelper->stopCapturing()); + + disconnect(Ekos::Manager::Instance()->schedulerModule(), &Ekos::Scheduler::newStatus, this, &TestEkosCaptureCount::schedulerStateChanged); + disconnect(Ekos::Manager::Instance()->captureModule(), &Ekos::Capture::captureComplete, this, &TestEkosCaptureCount::captureComplete); + + // clean up capture page + Ekos::Manager::Instance()->captureModule()->clearSequenceQueue(); + + // clean up expected images + m_expectedImages.clear(); + + // cleanup scheduler + cleanupScheduler(); +} + + +/* ********************************************************************************* + * + * Helper functions + * + * ********************************************************************************* */ + +bool TestEkosCaptureCount::checkCapturedFrames() +{ + bool success = true; + for (QMap::iterator it = m_expectedImages.begin(); it != m_expectedImages.end(); ++it) + if (it.value() != 0) + { + QWARN(QString("Capture count for signature %1 does not match: %2 frames too %3 captured.").arg(it.key()).arg(abs(it.value())).arg(it.value() < 0 ? "much" : "few").toStdString().c_str()); + success = false; + } + + return success; +} + +bool TestEkosCaptureCount::executeCapturing() +{ + + // calculate frame counts + int framesCount = 0; + for(int value: m_expectedImages.values()) + framesCount += value; + + // capture + KWRAP_SUB(QVERIFY(m_CaptureHelper->startCapturing(framesCount > 0))); + + // expect receiving a new CAPTURE_COMPLETE signal + if (framesCount > 0) + m_CaptureHelper->expectedCaptureStates.enqueue(Ekos::CAPTURE_COMPLETE); + + // wait for finish capturing + // ensure that the scheduler has started capturing + KWRAP_SUB(QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->expectedCaptureStates.size() == 0, 120000)); + + // verify whether all frames are captured as expected + KWRAP_SUB(QVERIFY2(checkCapturedFrames(), "Capturing did not produce the expected amount of frames.")); + + // wait for shutdown + QTest::qWait(5000); + return true; +} + +bool TestEkosCaptureCount::startEkosProfile() +{ + Ekos::Manager * const ekos = Ekos::Manager::Instance(); + + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(ekos->setupTab, 1000)); + + KWRAP_SUB(QVERIFY(m_CaptureHelper->setupEkosProfile("Simulators", false))); + // start the profile + KTRY_EKOS_CLICK(processINDIB); + // wait for the devices to come up + QTest::qWait(10000); + + // Everything completed successfully + return true; +} + +bool TestEkosCaptureCount::shutdownEkosProfile() +{ + qCInfo(KSTARS_EKOS_TEST) << "Stopping profile ..."; + KWRAP_SUB(KTRY_EKOS_STOP_SIMULATORS()); + qCInfo(KSTARS_EKOS_TEST) << "Stopping profile ... (done)"; + // Everything completed successfully + return true; +} + +bool TestEkosCaptureCount::prepareCapture() +{ + QFETCH(double, exptime); + QFETCH(QString, sequence); + QFETCH(QString, capturedFramesMap); + QFETCH(QString, expectedFrames); + QFETCH(int, iterations); + + // switch to capture module + Ekos::Capture *capture = Ekos::Manager::Instance()->captureModule(); + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(capture, 1000)); + + // add target to path to emulate the behavior of the scheduler + QString imagepath = getImageLocation()->path() + "/" + target; + + // create the destination for images + qCInfo(KSTARS_EKOS_TEST) << "FITS path: " << imagepath; + + // create capture sequences + for (int i = 0; i < iterations; i++) + KVERIFY_SUB(fillCaptureSequences(sequence, exptime, imagepath)); + + // fill the captured frames map that hold the numbers of already taken frames + KVERIFY_SUB(fillCapturedFramesMap(capturedFramesMap)); + + // fill the map of expected frames + KVERIFY_SUB(setExpectedFrames(expectedFrames)); + + // everything successfully completed + return true; +} + +bool TestEkosCaptureCount::prepareScheduledCapture() +{ + QFETCH(double, exptime); + QFETCH(QString, sequence); + QFETCH(QString, capturedFramesMap); + QFETCH(QString, expectedFrames); + QFETCH(int, iterations); + + // switch to capture module + Ekos::Capture *capture = Ekos::Manager::Instance()->captureModule(); + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(capture, 1000)); + + // create the destination for images + qCInfo(KSTARS_EKOS_TEST) << "FITS path: " << getImageLocation()->path(); + + // step 1: create the frames due to the captured frames map + if (capturedFramesMap != "") + { + KVERIFY_SUB(fillCaptureSequences(capturedFramesMap, exptime, getImageLocation()->filePath(target))); + KVERIFY_SUB(fillCapturedFramesMap("")); + KVERIFY_SUB(setExpectedFrames(capturedFramesMap)); + + // create the expected frames + KVERIFY_SUB(executeCapturing()); + + // clean up + capture->clearSequenceQueue(); + } + + // step 2: create the frames due to the captured frames map + KVERIFY_SUB(fillCaptureSequences(sequence, exptime, getImageLocation()->path())); + KVERIFY_SUB(fillCapturedFramesMap("")); + KVERIFY_SUB(setExpectedFrames(expectedFrames)); + + // save current capture sequence to Ekos sequence file + QString sequenceFile = destination->filePath("test.esq"); + qCInfo(KSTARS_EKOS_TEST) << "Sequence file" << sequenceFile << "created."; + KVERIFY_SUB(Ekos::Manager::Instance()->captureModule()->saveSequenceQueue(sequenceFile)); + + // setup scheduler + Ekos::Scheduler *scheduler = Ekos::Manager::Instance()->schedulerModule(); + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(scheduler, 1000)); + // set sequence file + scheduler->setSequence(sequenceFile); + // set Kocab as target + KTRY_SET_LINEEDIT_SUB(scheduler, nameEdit, target); + KTRY_SET_LINEEDIT_SUB(scheduler, raBox, "14 50 42"); + KTRY_SET_LINEEDIT_SUB(scheduler, decBox, "74 09 20"); + // disable all step checks + KTRY_SET_CHECKBOX_SUB(scheduler, trackStepCheck, false); + KTRY_SET_CHECKBOX_SUB(scheduler, focusStepCheck, false); + KTRY_SET_CHECKBOX_SUB(scheduler, alignStepCheck, false); + KTRY_SET_CHECKBOX_SUB(scheduler, guideStepCheck, false); + // ignore twilight + KTRY_SET_CHECKBOX_SUB(scheduler, twilightCheck, false); + // set remember job progress + Options::setRememberJobProgress(true); + // disable INDI stopping after scheduler finished + Options::setStopEkosAfterShutdown(false); + // repeat the job for a fixed amount + KTRY_SET_RADIOBUTTON_SUB(scheduler, repeatCompletionR, true); + KTRY_SET_SPINBOX_SUB(scheduler, repeatsSpin, iterations); + // add scheduler job + KTRY_CLICK_SUB(scheduler, addToQueueB); + KTRY_GADGET_SUB(scheduler, QTableWidget, queueTable); + KVERIFY_SUB(queueTable->rowCount() == 1); + KVERIFY_SUB(queueTable->columnCount() > 3); + + // verify the displayed capture counts + QString displayedCounts = queueTable->item(0, 2)->text(); + KVERIFY2_SUB(displayedCounts.indexOf("/") > 0, "Scheduler job table does not display in style captured/total."); + // check display of already captured + int captured = displayedCounts.left(displayedCounts.indexOf("/")).toInt(); + int captured_expected = totalCount(capturedFramesMap); + KVERIFY2_SUB(captured == captured_expected, + QString("Scheduler job table shows %1 captured frames instead of %2.").arg(captured).arg(captured_expected).toStdString().c_str()); + // check display of expected frames + int total = displayedCounts.right(displayedCounts.length() - displayedCounts.indexOf("/") - 1).toInt(); + int total_expected = totalCount(sequence) * iterations; + KVERIFY2_SUB(total == total_expected, + QString("Scheduler job table shows %1 expected frames instead of %2.").arg(total).arg(total_expected).toStdString().c_str()); + + // everything successfully completed + return true; +} + +void TestEkosCaptureCount::prepareTestData(double exptime, QString sequence, QString capturedFramesMap, QString expectedFrames, int iterations) +{ + + QTest::addColumn("exptime"); /*!< exposure time */ + QTest::addColumn("sequence"); /*!< list of filters */ + QTest::addColumn("capturedFramesMap"); /*!< list of frame counts */ + QTest::addColumn("expectedFrames"); /*!< list of frames per filter that are expected */ + QTest::addColumn("iterations"); /*!< how often should the sequence be repeated */ + + QTest::newRow(QString("seq=%1x, ex=%2, it=%3").arg(sequence).arg(capturedFramesMap).arg(iterations).toStdString().c_str()) + << exptime << sequence << capturedFramesMap << expectedFrames << iterations; + } + +QDir *TestEkosCaptureCount::getImageLocation() +{ + if (imageLocation == nullptr || imageLocation->exists()) + imageLocation = new QDir(destination->path() + "/images"); + + return imageLocation; +} + +QString TestEkosCaptureCount::calculateSignature(QString filter) +{ + if (target == "") + return getImageLocation()->path() + "/Light/" + filter + "/Light"; + else + return getImageLocation()->path() + "/" + target + "/Light/" + filter + "/" + target + "_Light"; +} + +bool TestEkosCaptureCount::fillCaptureSequences(QString sequence, double exptime, QString fitsDirectory) +{ + if (sequence == "") + return true; + + for (QString value : sequence.split(",")) + { + KVERIFY_SUB(value.indexOf(":") > -1); + QString filter = value.left(value.indexOf(":")); + int count = value.right(value.length()-value.indexOf(":")-1).toInt(); + KTRY_SET_CHECKBOX_SUB(Ekos::Manager::Instance()->captureModule(), fileTimestampS, true); + KTRY_SET_LINEEDIT_SUB(Ekos::Manager::Instance()->captureModule(), filePrefixT, target); + if (count > 0) + KWRAP_SUB(KTRY_CAPTURE_ADD_LIGHT(exptime, count, 0, filter, fitsDirectory)); + // ensure that no old values are present + Ekos::Manager::Instance()->captureModule()->setCapturedFramesMap(calculateSignature(filter), 0); + } + + return true; +} + +bool TestEkosCaptureCount::fillCapturedFramesMap(QString capturedFramesMap) +{ + if (capturedFramesMap != "") + { + for (QString value : capturedFramesMap.split(",")) + { + KVERIFY_SUB(value.indexOf(":") > -1); + QString filter = value.left(value.indexOf(":")); + int count = value.right(value.length()-value.indexOf(":")-1).toInt(); + Ekos::Manager::Instance()->captureModule()->setCapturedFramesMap(calculateSignature(filter), count); + } + } + + return true; +} + +bool TestEkosCaptureCount::setExpectedFrames(QString expectedFrames) +{ + if (expectedFrames != "") + { + for (QString value : expectedFrames.split(",")) + { + KVERIFY_SUB(value.indexOf(":") > -1); + QString filter = value.left(value.indexOf(":")); + int count = value.right(value.length()-value.indexOf(":")-1).toInt(); + m_expectedImages.insert(filter, count); + } + } + else + m_expectedImages.clear(); + + return true; +} + +int TestEkosCaptureCount::totalCount(QString sequence) +{ + if (sequence == "") + return 0; + + int total = 0; + for (QString value : sequence.split(",")) + total += value.right(value.length()-value.indexOf(":")-1).toInt(); + + return total; +} +/* ********************************************************************************* + * + * Slots + * + * ********************************************************************************* */ + +void TestEkosCaptureCount::captureComplete(const QString &filename, double exposureSeconds, const QString &filter, double hfr) +{ + Q_UNUSED(filename); + Q_UNUSED(exposureSeconds); + Q_UNUSED(hfr); + + // reduce the for the job's signature the number of expected images + m_expectedImages.insert(filter, m_expectedImages.value(filter, 0) - 1); +} + +void TestEkosCaptureCount::schedulerStateChanged(Ekos::SchedulerState status) +{ + m_SchedulerStatus = status; + // check if the new state is the next one expected, then remove it from the stack + if (!expectedSchedulerStates.isEmpty() && expectedSchedulerStates.head() == status) + expectedSchedulerStates.dequeue(); + +} + +/* ********************************************************************************* + * + * Main function + * + * ********************************************************************************* */ + +QTEST_KSTARS_MAIN(TestEkosCaptureCount) diff --git a/Tests/kstars_ui/test_ekos_capture_count.h b/Tests/kstars_ui/test_ekos_capture_count.h new file mode 100644 index 0000000000000000000000000000000000000000..563f11d45837dfa178a54fd897767d52db76b56a --- /dev/null +++ b/Tests/kstars_ui/test_ekos_capture_count.h @@ -0,0 +1,184 @@ +/* + KStars UI tests for verifying correct counting of the capture module + + Copyright (C) 2020 + Wolfgang Reissenberger + + 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 + +#include +#include + +#include "config-kstars.h" +#include "test_ekos_debug.h" + +#if defined(HAVE_INDI) +#include "ekos/ekos.h" +#include "ekos/capture/sequencejob.h" +#include "test_ekos_capture_helper.h" + +class TestEkosCaptureCount : public QObject +{ + Q_OBJECT +public: + explicit TestEkosCaptureCount(QObject *parent = nullptr); + +protected: + + // destination where images will be located + QTemporaryDir *destination; + QDir *imageLocation = nullptr; + + /** + * @brief Start a test EKOS profile. + */ + bool startEkosProfile(); + + /** + * @brief Shutdown the current test EKOS profile. + */ + bool shutdownEkosProfile(); + + /** + * @brief Setup capturing + * @return true iff preparation was successful + */ + bool prepareCapture(); + + /** + * @brief Setup capturing for tests with the scheduler + * @return true iff preparation was successful + */ + bool prepareScheduledCapture(); + + /** + * @brief Stop and clean up scheduler + */ + void cleanupScheduler(); + + /** + * @brief Execute capturing + * @return true iff exactly the expected frames have been taken + */ + bool executeCapturing(); + + /** + * @brief Helper function translating simple QString input into QTest test data rows + * @param exptime exposure time of the sequence + * @param sequence filter and count as QString("::: m_expectedImages; + /** + * @brief Register that a new image has been captured + */ + void captureComplete(const QString &filename, double exposureSeconds, const QString &filter, double hfr); + + // sequence of scheduler states that are expected + QQueue expectedSchedulerStates; + /** + * @brief Slot to receive a new scheduler state + * @param status new capture status + */ + void schedulerStateChanged(Ekos::SchedulerState status); + + // current scheduler status + Ekos::SchedulerState m_SchedulerStatus; + + /** + * @brief Retrieve the current capture status. + */ + inline Ekos::SchedulerState getSchedulerStatus() {return m_SchedulerStatus;} + + +protected slots: + void initTestCase(); + void cleanupTestCase(); + + void init(); + void cleanup(); + +private: + // current capture status + Ekos::CaptureState m_CaptureStatus; + + // helper class + TestEkosCaptureHelper *m_CaptureHelper = nullptr; + + QString target = "test"; + + /** + * @brief Fill the capture sequences in the Capture GUI + * @param expectedFrames comma separated list of : + * @param exptime exposure time + * @param fitsDirectory directory where the captures will be placed + * @return true if everything was successful + */ + bool fillCaptureSequences(QString sequence, double exptime, QString fitsDirectory); + + /** + * @brief Fill the map of frames that have already been captured + * @param expectedFrames comma separated list of : + * @return true if everything was successful + */ + bool fillCapturedFramesMap(QString capturedFramesMap); + + /** + * @brief Determine the total count from a comma separated sequence of : + * @return sum of + */ + int totalCount(QString sequence); + + /** + * @brief Fill the map of frames that are expected to be captured + * @param expectedFrames comma separated list of : + * @return true if everything was successful + */ + bool setExpectedFrames(QString expectedFrames); + + + /** + * @brief Check if the expected number of frames are captured + * @return true if yes + */ + bool checkCapturedFrames(); + + /** + * @brief calculateSignature Calculate the signature of a given filter + * @param filter filter name + * @return signature + */ + QString calculateSignature(QString filter); + + QDir *getImageLocation(); + +private slots: + /** + * @brief Test whether the capture module produces exactly the diff between the capture frames map and the defined frame counts. + */ + void testCaptureWithCaptureFramesMap(); + + /** @brief Test data for @see testCaptureWithCaptureFramesMap() */ + void testCaptureWithCaptureFramesMap_data(); + + /** + * @brief Test of appropriate captures controlled by the scheduler + */ + void testSchedulerCapture(); + + /** @brief Test data for @see testSchedulerCapture() */ + void testSchedulerCapture_data(); +}; + +#endif // HAVE_INDI diff --git a/Tests/kstars_ui/test_ekos_capture_helper.cpp b/Tests/kstars_ui/test_ekos_capture_helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..824d4119fb987e64c6ca2db46a098af4263a0622 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_capture_helper.cpp @@ -0,0 +1,232 @@ +/* + Helper class of KStars UI capture tests + + Copyright (C) 2020 + Wolfgang Reissenberger + + 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_capture_helper.h" + +#include "test_ekos.h" + +TestEkosCaptureHelper::TestEkosCaptureHelper() {} + +void TestEkosCaptureHelper::initTestCase() +{ + // connect to the capture process to receive capture status changes + connect(Ekos::Manager::Instance()->captureModule(), &Ekos::Capture::newStatus, this, &TestEkosCaptureHelper::captureStatusChanged, + Qt::UniqueConnection); +} + +void TestEkosCaptureHelper::cleanupTestCase() +{ + // disconnect to the capture process to receive capture status changes + disconnect(Ekos::Manager::Instance()->captureModule(), &Ekos::Capture::newStatus, this, &TestEkosCaptureHelper::captureStatusChanged); +} + +void TestEkosCaptureHelper::fillProfile(bool *isDone) +{ + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: starting..."; + ProfileEditor* profileEditor = Ekos::Manager::Instance()->findChild("profileEditorDialog"); + + // Select the mount device + KTRY_PROFILEEDITOR_GADGET(QComboBox, mountCombo); + setTreeviewCombo(mountCombo, m_MountDevice); + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Mount selected."; + + // Selet the CCD device + KTRY_PROFILEEDITOR_GADGET(QComboBox, ccdCombo); + setTreeviewCombo(ccdCombo, m_CCDDevice); + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: CCD selected."; + + // Select the focuser device + KTRY_PROFILEEDITOR_GADGET(QComboBox, focuserCombo); + setTreeviewCombo(focuserCombo, m_FocuserDevice); + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Focuser selected."; + + // Select the guider device + KTRY_PROFILEEDITOR_GADGET(QComboBox, guiderCombo); + setTreeviewCombo(guiderCombo, m_GuiderDevice); + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Guider selected."; + + // wait a short time to make the setup visible + QTest::qWait(1000); + // Save the profile using the "Save" button + QDialogButtonBox* buttons = profileEditor->findChild("dialogButtons"); + QVERIFY(nullptr != buttons); + QTest::mouseClick(buttons->button(QDialogButtonBox::Save), Qt::LeftButton); + + qCInfo(KSTARS_EKOS_TEST) << "Fill profile: Selections saved."; + + *isDone = true; +} + +void TestEkosCaptureHelper::createEkosProfile(QString name, bool isPHD2, bool *isDone) +{ + ProfileEditor* profileEditor = Ekos::Manager::Instance()->findChild("profileEditorDialog"); + + // Set the profile name + KTRY_SET_LINEEDIT(profileEditor, profileIN, name); + // select the guider type + KTRY_SET_COMBO(profileEditor, guideTypeCombo, isPHD2 ? "PHD2" : "Internal"); + if (isPHD2) + { + // Write PHD2 server specs + KTRY_SET_LINEEDIT(profileEditor, externalGuideHost, "localhost"); + KTRY_SET_LINEEDIT(profileEditor, externalGuidePort, "4400"); + } + + qCInfo(KSTARS_EKOS_TEST) << "Ekos profile " << name << " created."; + // and now continue with filling the profile + fillProfile(isDone); +} + +bool TestEkosCaptureHelper::setupEkosProfile(QString name, bool isPHD2) +{ + qCInfo(KSTARS_EKOS_TEST) << "Setting up Ekos profile..."; + bool isDone = false; + Ekos::Manager * const ekos = Ekos::Manager::Instance(); + // check if the profile with the given name exists + KTRY_GADGET_SUB(ekos, QComboBox, profileCombo); + if (profileCombo->findText(name) >= 0) + { + KTRY_GADGET_SUB(ekos, QPushButton, editProfileB); + + // edit Simulators profile + KWRAP_SUB(KTRY_EKOS_SELECT_PROFILE(name)); + + // edit only editable profiles + if (editProfileB->isEnabled()) + { + // start with a delay of 1 sec a new thread that edits the profile + QTimer::singleShot(1000, ekos, [&]{fillProfile(&isDone);}); + KTRY_CLICK_SUB(ekos, editProfileB); + } + else + { + qCInfo(KSTARS_EKOS_TEST) << "Profile " << name << " not editable, setup finished."; + isDone = true; + return true; + } + } + else + { + // start with a delay of 1 sec a new thread that edits the profile + qCInfo(KSTARS_EKOS_TEST) << "Creating new profile " << name << " ..."; + QTimer::singleShot(1000, ekos, [&]{createEkosProfile(name, isPHD2, &isDone);}); + // create new profile addProfileB + KTRY_CLICK_SUB(ekos, addProfileB); + } + + + // Cancel the profile editor if test did not close the editor dialog within 10 sec + QTimer * closeDialog = new QTimer(this); + closeDialog->setSingleShot(true); + closeDialog->setInterval(10000); + ekos->connect(closeDialog, &QTimer::timeout, [&] + { + ProfileEditor* profileEditor = ekos->findChild("profileEditorDialog"); + if (profileEditor != nullptr) + { + profileEditor->reject(); + qWarning(KSTARS_EKOS_TEST) << "Editing profile aborted."; + } + }); + + + // Click handler returned, stop the timer closing the dialog on failure + closeDialog->stop(); + delete closeDialog; + + // Verification of the first test step + return isDone; + +} + + +bool TestEkosCaptureHelper::startCapturing(bool checkCapturing) +{ + // switch to the capture module + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->captureModule(), 1000)); + + // check if capture is in a stopped state + KWRAP_SUB(QVERIFY(getCaptureStatus() == Ekos::CAPTURE_IDLE || getCaptureStatus() == Ekos::CAPTURE_ABORTED + || getCaptureStatus() == Ekos::CAPTURE_COMPLETE)); + + // ensure at least one capture is started if requested + if (checkCapturing) + expectedCaptureStates.enqueue(Ekos::CAPTURE_CAPTURING); + // press start + KTRY_GADGET_SUB(Ekos::Manager::Instance()->captureModule(), QPushButton, startB); + KTRY_CLICK_SUB(Ekos::Manager::Instance()->captureModule(), startB); + + // check if capturing has started + KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedCaptureStates, 5000); + // all checks succeeded + return true; +} + +bool TestEkosCaptureHelper::stopCapturing() +{ + // check if capture is in a stopped state + if (getCaptureStatus() == Ekos::CAPTURE_IDLE || getCaptureStatus() == Ekos::CAPTURE_ABORTED || getCaptureStatus() == Ekos::CAPTURE_COMPLETE) + return true; + + // switch to the capture module + KWRAP_SUB(KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(Ekos::Manager::Instance()->captureModule(), 1000)); + + // else press stop + expectedCaptureStates.enqueue(Ekos::CAPTURE_ABORTED); + KTRY_GADGET_SUB(Ekos::Manager::Instance()->captureModule(), QPushButton, startB); + KTRY_CLICK_SUB(Ekos::Manager::Instance()->captureModule(), startB); + + // check if capturing has stopped + KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(expectedCaptureStates, 5000); + // all checks succeeded + return true; +} + +/* ********************************************************************************* + * + * Helper functions + * + * ********************************************************************************* */ +void TestEkosCaptureHelper::setTreeviewCombo(QComboBox *combo, QString lookup) +{ + // Match the text recursively in the model, this results in a model index with a parent + QModelIndexList const list = combo->model()->match(combo->model()->index(0, 0), Qt::DisplayRole, QVariant::fromValue(lookup), 1, Qt::MatchRecursive); + QVERIFY(0 < list.count()); + QModelIndex const &index = list.first(); + QCOMPARE(list.value(0).data().toString(), lookup); + QVERIFY(!index.parent().parent().isValid()); + // Now set the combobox model root to the match's parent + combo->setRootModelIndex(index.parent()); + combo->setModelColumn(index.column()); + combo->setCurrentIndex(index.row()); + + // Now reset + combo->setRootModelIndex(QModelIndex()); + combo->view()->setCurrentIndex(index); + + // Check, if everything went well + QCOMPARE(combo->currentText(), lookup); +} + +/* ********************************************************************************* + * + * Slots for catching state changes + * + * ********************************************************************************* */ + +void TestEkosCaptureHelper::captureStatusChanged(Ekos::CaptureState status) { + m_CaptureStatus = status; + // check if the new state is the next one expected, then remove it from the stack + if (!expectedCaptureStates.isEmpty() && expectedCaptureStates.head() == status) + expectedCaptureStates.dequeue(); +} + diff --git a/Tests/kstars_ui/test_ekos_capture_helper.h b/Tests/kstars_ui/test_ekos_capture_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..07d6bd62678f9e2d995af8ee7f3f927feb258787 --- /dev/null +++ b/Tests/kstars_ui/test_ekos_capture_helper.h @@ -0,0 +1,368 @@ +/* + Helper class of KStars UI capture tests + + Copyright (C) 2020 + Wolfgang Reissenberger + + 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 "test_ekos_debug.h" +#include "test_ekos_simulator.h" +#include "test_ekos_capture.h" + +#include "ekos/profileeditor.h" + +#include + +#pragma once + +/** + * @brief Helper function to verify the execution of a statement. + * + * If the result is false, this function immediately returns with false, + * otherwise simply continues. + * Use this function in subroutines of test cases which should return bool. + * @param statement expression to be verified + * @return false if statement equals false, otherwise continuing + */ +#define KVERIFY_SUB(statement) \ +do {\ + if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__))\ + return false;\ +} while (false) + +/** + * @brief Subroutine version of QVERIFY2 + * @return false if statement equals false, otherwise continuing + */ +#define KVERIFY2_SUB(statement, description) \ +do {\ + if (statement) {\ + if (!QTest::qVerify(true, #statement, (description), __FILE__, __LINE__))\ + return false;\ + } else {\ + if (!QTest::qVerify(false, #statement, (description), __FILE__, __LINE__))\ + return false;\ + }\ +} while (false) + +/** + * @brief Helper macro to wrap statements with test macros that returns false if + * the given statement preliminary invokes return (due to a test failure inside). + * @return false if statement equals false, otherwise continuing + */ +#define KWRAP_SUB(statement) \ +{ bool passed = false; \ + [&]() { statement; passed = true;}(); \ + if (!passed) return false; \ +} while (false); + +/** + * @brief Subroutine version of QTRY_TIMEOUT_DEBUG_IMPL + * @return false if expression equals false, otherwise continuing + */ +#define KTRY_TIMEOUT_DEBUG_IMPL_SUB(expr, timeoutValue, step)\ + if (!(expr)) { \ + QTRY_LOOP_IMPL((expr), (2 * timeoutValue), step);\ + if (expr) { \ + QString msg = QString::fromUtf8("QTestLib: This test case check (\"%1\") failed because the requested timeout (%2 ms) was too short, %3 ms would have been sufficient this time."); \ + msg = msg.arg(QString::fromUtf8(#expr)).arg(timeoutValue).arg(timeoutValue + qt_test_i); \ + KVERIFY2_SUB(false, qPrintable(msg)); \ + } \ + } + +/** + * @brief Subroutine version of QTRY_IMPL + * @return false if expression equals false, otherwise continuing + */ +#define KTRY_IMPL_SUB(expr, timeout)\ + const int qt_test_step = 50; \ + const int qt_test_timeoutValue = timeout; \ + QTRY_LOOP_IMPL((expr), qt_test_timeoutValue, qt_test_step); \ + KTRY_TIMEOUT_DEBUG_IMPL_SUB((expr), qt_test_timeoutValue, qt_test_step)\ + +/** + * @brief Subroutine version of QTRY_VERIFY_WITH_TIMEOUT + * @param expr expression to be verified + * @param timeout max time until the expression must become true + * @return false if statement equals false, otherwise continuing + */ +#define KTRY_VERIFY_WITH_TIMEOUT_SUB(expr, timeout) \ + do { \ + KTRY_IMPL_SUB((expr), timeout);\ + KVERIFY_SUB(expr); \ + } while (false) + +/** @brief Helper to retrieve a gadget from a certain module view. + * @param module KStars module that holds the checkox + * @param klass is the class of the gadget to look for. + * @param name is the gadget name to look for in the UI configuration. + * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Mount module + */ +#define KTRY_GADGET(module, klass, name) klass * const name = module->findChild(#name); \ + QVERIFY2(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) + +/** @brief Helper to retrieve a gadget from a certain module view (subroutine version). + * @param module KStars module that holds the checkox + * @param klass is the class of the gadget to look for. + * @param name is the gadget name to look for in the UI configuration. + * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Mount module + */ +#define KTRY_GADGET_SUB(module, klass, name) klass * const name = module->findChild(#name); \ + KVERIFY2_SUB(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) + +/** @brief Helper to click a button from a certain module view. + * @param module KStars module that holds the checkox + * @param button is the gadget name of the button to look for in the UI configuration. + * @warning Fails the test if the button is not currently enabled. + */ +#define KTRY_CLICK(module, button) do { \ + QTimer::singleShot(100, Ekos::Manager::Instance(), [&]() { \ + KTRY_GADGET(module, QPushButton, button); \ + QVERIFY2(button->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(#button).toStdString().c_str()); \ + QTest::mouseClick(button, Qt::LeftButton); }); \ + QTest::qWait(200); } while(false) + +/** @brief Helper to click a button from a certain module view (subroutine version for KTRY_CLICK). + * @param module KStars module that holds the checkox + * @param button is the gadget name of the button to look for in the UI configuration. + * @warning Fails the test if the button is not currently enabled. + */ +#define KTRY_CLICK_SUB(module, button) do { \ + bool success = false; \ + QTimer::singleShot(100, Ekos::Manager::Instance(), [&]() { \ + KTRY_GADGET(module, QPushButton, button); \ + QVERIFY2(button->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(#button).toStdString().c_str()); \ + QTest::mouseClick(button, Qt::LeftButton); success = true;}); \ + KTRY_VERIFY_WITH_TIMEOUT_SUB(success, 1000);} while(false) + +/** @brief Helper to set a checkbox and verify whether it succeeded + * @param module KStars module that holds the checkox + * @param checkbox object name of the checkbox + * @param value value the checkbox should be set + */ +#define KTRY_SET_CHECKBOX(module, checkbox, value) \ + KTRY_GADGET(module, QCheckBox, checkbox); checkbox->setChecked(value); QVERIFY(checkbox->isChecked() == value) + +/** @brief Subroutine version of @see KTRY_SET_CHECKBOX + * @param module KStars module that holds the checkox + * @param checkbox object name of the checkbox + * @param value value the checkbox should be set + */ +#define KTRY_SET_CHECKBOX_SUB(module, checkbox, value) \ + KWRAP_SUB(KTRY_SET_CHECKBOX(module, checkbox, value)) + +/** @brief Helper to set a radiobutton and verify whether it succeeded + * @param module KStars module that holds the radiobutton + * @param checkbox object name of the radiobutton + * @param value value the radiobutton should be set + */ +#define KTRY_SET_RADIOBUTTON(module, radiobutton, value) \ + KTRY_GADGET(module, QRadioButton, radiobutton); radiobutton->setChecked(value); QVERIFY(radiobutton->isChecked() == value) + +/** @brief Subroutine version of @see KTRY_SET_RADIOBUTTON + * @param module KStars module that holds the radiobutton + * @param checkbox object name of the radiobutton + * @param value value the radiobutton should be set + */ +#define KTRY_SET_RADIOBUTTON_SUB(module, radiobutton, value) \ + KWRAP_SUB(KTRY_SET_RADIOBUTTON(module, radiobutton, value)) + +/** @brief Helper to set a spinbox and verify whether it succeeded + * @param module KStars module that holds the spinbox + * @param spinbox object name of the spinbox + * @param value value the spinbox should be set + */ +#define KTRY_SET_SPINBOX(module, spinbox, x) \ + KTRY_GADGET(module, QSpinBox, spinbox); spinbox->setValue(x); QVERIFY(spinbox->value() == x) + +/** @brief Subroutine version of @see KTRY_SET_SPINBOX + * @param module KStars module that holds the spinbox + * @param spinbox object name of the spinbox + * @param value value the spinbox should be set + */ +#define KTRY_SET_SPINBOX_SUB(module, spinbox, x) \ + KWRAP_SUB(KTRY_SET_SPINBOX(module, spinbox, x)) + +/** @brief Helper to set a doublespinbox and verify whether it succeeded + * @param module KStars module that holds the spinbox + * @param spinbox object name of the spinbox + * @param value value the spinbox should be set + */ +#define KTRY_SET_DOUBLESPINBOX_SUB(module, spinbox, x) \ + KWRAP_SUB(KTRY_GADGET(module, QDoubleSpinBox, spinbox); spinbox->setValue(x); QVERIFY((spinbox->value() - x < 0.001))) + +/** @brief Helper to set a combo box and verify whether it succeeded + * @param module KStars module that holds the combo box + * @param combo object name of the combo box + * @param value value the combo box should be set + */ +#define KTRY_SET_COMBO(module, combo, value) \ + KTRY_GADGET(module, QComboBox, combo); combo->setCurrentText(value); QVERIFY(combo->currentText() == value) + +/** @brief Subroutine version of @see KTRY_SET_COMBO + * @param module KStars module that holds the combo box + * @param combo object name of the combo box + * @param value value the combo box should be set + */ +#define KTRY_SET_COMBO_SUB(module, combo, value) \ + KWRAP_SUB(KTRY_GADGET(module, QComboBox, combo); combo->setCurrentText(value); QVERIFY(combo->currentText() == value)) + +/** @brief Helper to set a combo box by indexand verify whether it succeeded + * @param module KStars module that holds the combo box + * @param combo object name of the combo box + * @param value index the combo box should be selected + */ +#define KTRY_SET_COMBO_INDEX_SUB(module, combo, value) \ + KWRAP_SUB(KTRY_GADGET(module, QComboBox, combo); combo->setCurrentIndex(value); QVERIFY(combo->currentIndex() == value)) + +/** @brief Helper to set a line edit box and verify whether it succeeded + * @param module KStars module that holds the line edit box + * @param spinbox object name of the line edit box + * @param value value the line edit box should be set + */ +#define KTRY_SET_LINEEDIT(module, lineedit, value) \ + KTRY_GADGET(module, QLineEdit, lineedit); lineedit->setText(value); QVERIFY((lineedit->text() == value)) + +/** @brief Subroutine version of @see KTRY_SET_LINEEDIT + * @param module KStars module that holds the line edit box + * @param spinbox object name of the line edit box + * @param value value the line edit box should be set + */ +#define KTRY_SET_LINEEDIT_SUB(module, lineedit, value) \ + KWRAP_SUB(KTRY_GADGET(module, QLineEdit, lineedit); lineedit->setText(value); QVERIFY((lineedit->text() == value))) + +/** + * @brief Helper to check whether a state queue is empty after the given delay + * @param queue event queue + * @param delay in milliseconds + */ +#define KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(queue, delay) \ + if (! QTest::qWaitFor([&](){return queue.isEmpty();}, delay)) { \ + QString result("States not reached: "); \ + QTextStream stream(&result); \ + while (!(queue).isEmpty()) stream << (queue).dequeue(); \ + QFAIL(qPrintable(result));} + +/** + * @brief Helper to check whether a state queue is empty after the given delay + * @param queue event queue + * @param delay in milliseconds + */ +#define KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(queue, delay) \ + if (! QTest::qWaitFor([&](){return queue.isEmpty();}, delay)) { \ + QString result("States not reached: "); \ + QTextStream stream(&result); \ + while (!(queue).isEmpty()) stream << (queue).dequeue(); \ + QWARN(qPrintable(result)); return false;} + +/** + * @brief Helper to verify if the text in the text field starts with the given text + * @param field UI Text field + * @param text Text the text field should start with + * @param timeout in ms + */ +#define KTRY_VERIFY_TEXTFIELD_STARTS_WITH_TIMEOUT_SUB(field, title, timeout) \ + KTRY_VERIFY_WITH_TIMEOUT_SUB(field->text().length() >= QString(title).length() && \ + field->text().left(QString(title).length()).compare(title) == 0, timeout) + +/** + * @brief Helper function for switching to a certain module + * @param module target module + * @param timeout in ms + */ +#define KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(module, timeout) do {\ + KTRY_EKOS_GADGET(QTabWidget, toolsWidget); \ + toolsWidget->setCurrentWidget(module); \ + QTRY_COMPARE_WITH_TIMEOUT(toolsWidget->currentWidget(), module, timeout);} while (false) + + +class TestEkosCaptureHelper : public QObject +{ + Q_OBJECT + +public: + + explicit TestEkosCaptureHelper(); + + // Mount device + QString m_MountDevice = "Telescope Simulator"; + // CCD device + QString m_CCDDevice = "CCD Simulator"; + // Guiding device + QString m_GuiderDevice = "Guide Simulator"; + // Focusing device + QString m_FocuserDevice = "Focuser Simulator"; + + /** + * @brief Initialization ahead of executing the test cases. + */ + void initTestCase(); + + /** + * @brief Cleanup after test cases have been executed. + */ + void cleanupTestCase(); + + /** + * @brief Configure the EKOS profile + * @param name of the profile + * @param isPHD2 use internal guider or PHD2 + */ + bool setupEkosProfile(QString name, bool isPHD2); + + /** + * @brief create a new EKOS profile + * @param name name of the profile + * @param isPHD2 use internal guider or PHD2 + * @param isDone will be true if everything succeeds + */ + void createEkosProfile(QString name, bool isPHD2, bool *isDone); + + /** + * @brief Fill mount, guider, CCD and focuser of an EKOS profile + * @param isDone will be true if everything succeeds + */ + void fillProfile(bool *isDone); + + /** + * @brief Helper function for start of capturing + * @param checkCapturing set to true if check of capturing should be included + */ + bool startCapturing(bool checkCapturing = true); + + /** + * @brief Helper function to stop capturing + */ + bool stopCapturing(); + + /** + * @brief Set a tree view combo to a given value + * @param combo box with tree view + * @param lookup target value + */ + void setTreeviewCombo(QComboBox *combo, const QString lookup); + + + // sequence of capture states that are expected + QQueue expectedCaptureStates; + + /** + * @brief Slot to track the capture status + * @param status new capture status + */ + void captureStatusChanged(Ekos::CaptureState status); + + // current capture status + Ekos::CaptureState m_CaptureStatus; + + /** + * @brief Retrieve the current capture status. + */ + inline Ekos::CaptureState getCaptureStatus() {return m_CaptureStatus;} +}; diff --git a/Tests/kstars_ui/test_ekos_meridianflip_base.h b/Tests/kstars_ui/test_ekos_meridianflip_base.h index f64b6bd005b2156f0da6a301042b31bf86e4afbb..e315bde15dfe1ffc04de84a24da3ea292b79e520 100644 --- a/Tests/kstars_ui/test_ekos_meridianflip_base.h +++ b/Tests/kstars_ui/test_ekos_meridianflip_base.h @@ -30,242 +30,7 @@ #include "test_ekos_simulator.h" #include "test_ekos_capture.h" - -/** - * @brief Helper function to verify the execution of a statement. - * - * If the result is false, this function immediately returns with false, - * otherwise simply continues. - * Use this function in subroutines of test cases which should return bool. - * @param statement expression to be verified - * @return false if statement equals false, otherwise continuing - */ -#define KVERIFY_SUB(statement) \ -do {\ - if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__))\ - return false;\ -} while (false) - -/** - * @brief Subroutine version of QVERIFY2 - * @return false if statement equals false, otherwise continuing - */ -#define KVERIFY2_SUB(statement, description) \ -do {\ - if (statement) {\ - if (!QTest::qVerify(true, #statement, (description), __FILE__, __LINE__))\ - return false;\ - } else {\ - if (!QTest::qVerify(false, #statement, (description), __FILE__, __LINE__))\ - return false;\ - }\ -} while (false) - -/** - * @brief Helper macro to wrap statements with test macros that returns false if - * the given statement preliminary invokes return (due to a test failure inside). - * @return false if statement equals false, otherwise continuing - */ -#define KWRAP_SUB(statement) \ -{ bool passed = false; \ - [&]() { statement; passed = true;}(); \ - if (!passed) return false; \ -} while (false); - -/** - * @brief Subroutine version of QTRY_TIMEOUT_DEBUG_IMPL - * @return false if expression equals false, otherwise continuing - */ -#define KTRY_TIMEOUT_DEBUG_IMPL_SUB(expr, timeoutValue, step)\ - if (!(expr)) { \ - QTRY_LOOP_IMPL((expr), (2 * timeoutValue), step);\ - if (expr) { \ - QString msg = QString::fromUtf8("QTestLib: This test case check (\"%1\") failed because the requested timeout (%2 ms) was too short, %3 ms would have been sufficient this time."); \ - msg = msg.arg(QString::fromUtf8(#expr)).arg(timeoutValue).arg(timeoutValue + qt_test_i); \ - KVERIFY2_SUB(false, qPrintable(msg)); \ - } \ - } - -/** - * @brief Subroutine version of QTRY_IMPL - * @return false if expression equals false, otherwise continuing - */ -#define KTRY_IMPL_SUB(expr, timeout)\ - const int qt_test_step = 50; \ - const int qt_test_timeoutValue = timeout; \ - QTRY_LOOP_IMPL((expr), qt_test_timeoutValue, qt_test_step); \ - KTRY_TIMEOUT_DEBUG_IMPL_SUB((expr), qt_test_timeoutValue, qt_test_step)\ - -/** - * @brief Subroutine version of QTRY_VERIFY_WITH_TIMEOUT - * @param expr expression to be verified - * @param timeout max time until the expression must become true - * @return false if statement equals false, otherwise continuing - */ -#define KTRY_VERIFY_WITH_TIMEOUT_SUB(expr, timeout) \ - do { \ - KTRY_IMPL_SUB((expr), timeout);\ - KVERIFY_SUB(expr); \ - } while (false) - -/** @brief Helper to retrieve a gadget from a certain module view. - * @param module KStars module that holds the checkox - * @param klass is the class of the gadget to look for. - * @param name is the gadget name to look for in the UI configuration. - * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Mount module - */ -#define KTRY_GADGET(module, klass, name) klass * const name = module->findChild(#name); \ - QVERIFY2(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) - -/** @brief Helper to retrieve a gadget from a certain module view (subroutine version). - * @param module KStars module that holds the checkox - * @param klass is the class of the gadget to look for. - * @param name is the gadget name to look for in the UI configuration. - * @warning Fails the test if the gadget "name" of class "klass" does not exist in the Mount module - */ -#define KTRY_GADGET_SUB(module, klass, name) klass * const name = module->findChild(#name); \ - KVERIFY2_SUB(name != nullptr, QString(#klass " '%1' does not exist and cannot be used").arg(#name).toStdString().c_str()) - -/** @brief Helper to click a button from a certain module view. - * @param module KStars module that holds the checkox - * @param button is the gadget name of the button to look for in the UI configuration. - * @warning Fails the test if the button is not currently enabled. - */ -#define KTRY_CLICK(module, button) do { \ - QTimer::singleShot(100, Ekos::Manager::Instance(), []() { \ - KTRY_GADGET(module, QPushButton, button); \ - QVERIFY2(button->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(#button).toStdString().c_str()); \ - QTest::mouseClick(button, Qt::LeftButton); }); \ - QTest::qWait(200); } while(false) - -/** @brief Helper to click a button from a certain module view (subroutine version for KTRY_CLICK). - * @param module KStars module that holds the checkox - * @param button is the gadget name of the button to look for in the UI configuration. - * @warning Fails the test if the button is not currently enabled. - */ -#define KTRY_CLICK_SUB(module, button) do { \ - bool success = false; \ - QTimer::singleShot(100, Ekos::Manager::Instance(), [&]() { \ - KTRY_GADGET(module, QPushButton, button); \ - QVERIFY2(button->isEnabled(), QString("QPushButton '%1' is disabled and cannot be clicked").arg(#button).toStdString().c_str()); \ - QTest::mouseClick(button, Qt::LeftButton); success = true;}); \ - KTRY_VERIFY_WITH_TIMEOUT_SUB(success, 1000);} while(false) - -/** @brief Helper to set a checkbox and verify whether it succeeded (subroutine version) - * @param module KStars module that holds the checkox - * @param checkbox object name of the checkbox - * @param value value the checkbox should be set - */ -#define KTRY_SET_CHECKBOX_SUB(module, checkbox, value) \ - KWRAP_SUB(KTRY_GADGET(module, QCheckBox, checkbox); checkbox->setChecked(value); QVERIFY(checkbox->isChecked() == value)) - -/** @brief Helper to set a radiobutton and verify whether it succeeded - * @param module KStars module that holds the radiobutton - * @param checkbox object name of the radiobutton - * @param value value the radiobutton should be set - */ -#define KTRY_SET_RADIOBUTTON_SUB(module, radiobutton, value) \ - KWRAP_SUB(KTRY_GADGET(module, QRadioButton, radiobutton); radiobutton->setChecked(value); QVERIFY(radiobutton->isChecked() == value)) - -/** @brief Helper to set a spinbox and verify whether it succeeded - * @param module KStars module that holds the spinbox - * @param spinbox object name of the spinbox - * @param value value the spinbox should be set - */ -#define KTRY_SET_SPINBOX_SUB(module, spinbox, x) \ - KWRAP_SUB(KTRY_GADGET(module, QSpinBox, spinbox); spinbox->setValue(x); QVERIFY(spinbox->value() == x)) - -/** @brief Helper to set a doublespinbox and verify whether it succeeded - * @param module KStars module that holds the spinbox - * @param spinbox object name of the spinbox - * @param value value the spinbox should be set - */ -#define KTRY_SET_DOUBLESPINBOX_SUB(module, spinbox, x) \ - KWRAP_SUB(KTRY_GADGET(module, QDoubleSpinBox, spinbox); spinbox->setValue(x); QVERIFY((spinbox->value() - x < 0.001))) - -/** @brief Helper to set a combo box and verify whether it succeeded - * @param module KStars module that holds the combo box - * @param combo object name of the combo box - * @param value value the combo box should be set - */ -#define KTRY_SET_COMBO(module, combo, value) \ - KTRY_GADGET(module, QComboBox, combo); combo->setCurrentText(value); QVERIFY(combo->currentText() == value) - -/** @brief Subroutine version of @see KTRY_SET_COMBO - * @param module KStars module that holds the combo box - * @param combo object name of the combo box - * @param value value the combo box should be set - */ -#define KTRY_SET_COMBO_SUB(module, combo, value) \ - KWRAP_SUB(KTRY_GADGET(module, QComboBox, combo); combo->setCurrentText(value); QVERIFY(combo->currentText() == value)) - -/** @brief Helper to set a combo box by indexand verify whether it succeeded - * @param module KStars module that holds the combo box - * @param combo object name of the combo box - * @param value index the combo box should be selected - */ -#define KTRY_SET_COMBO_INDEX_SUB(module, combo, value) \ - KWRAP_SUB(KTRY_GADGET(module, QComboBox, combo); combo->setCurrentIndex(value); QVERIFY(combo->currentIndex() == value)) - -/** @brief Helper to set a line edit box and verify whether it succeeded - * @param module KStars module that holds the line edit box - * @param spinbox object name of the line edit box - * @param value value the line edit box should be set - */ -#define KTRY_SET_LINEEDIT(module, lineedit, value) \ - KTRY_GADGET(module, QLineEdit, lineedit); lineedit->setText(value); QVERIFY((lineedit->text() == value)) - -/** @brief Subroutine version of @see KTRY_SET_LINEEDIT - * @param module KStars module that holds the line edit box - * @param spinbox object name of the line edit box - * @param value value the line edit box should be set - */ -#define KTRY_SET_LINEEDIT_SUB(module, lineedit, value) \ - KWRAP_SUB(KTRY_GADGET(module, QLineEdit, lineedit); lineedit->setText(value); QVERIFY((lineedit->text() == value))) - -/** - * @brief Helper to verify if the text in the text field starts with the given text - * @param field UI Text field - * @param text Text the text field should start with - * @param timeout in ms - */ -#define KTRY_VERIFY_TEXTFIELD_STARTS_WITH_TIMEOUT_SUB(field, title, timeout) \ - KTRY_VERIFY_WITH_TIMEOUT_SUB(field->text().length() >= QString(title).length() && \ - field->text().left(QString(title).length()).compare(title) == 0, timeout) - -/** - * @brief Helper to check whether a state queue is empty after the given delay - * @param queue event queue - * @param delay in milliseconds - */ -#define KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT(queue, delay) \ - if (! QTest::qWaitFor([&](){return queue.isEmpty();}, delay)) { \ - QString result("States not reached: "); \ - QTextStream stream(&result); \ - while (!(queue).isEmpty()) stream << (queue).dequeue(); \ - QFAIL(qPrintable(result));} - -/** - * @brief Helper to check whether a state queue is empty after the given delay - * @param queue event queue - * @param delay in milliseconds - */ -#define KVERIFY_EMPTY_QUEUE_WITH_TIMEOUT_SUB(queue, delay) \ - if (! QTest::qWaitFor([&](){return queue.isEmpty();}, delay)) { \ - QString result("States not reached: "); \ - QTextStream stream(&result); \ - while (!(queue).isEmpty()) stream << (queue).dequeue(); \ - QWARN(qPrintable(result)); return false;} - -/** - * @brief Helper function for switching to a certain module - * @param module target module - * @param timeout in ms - */ -#define KTRY_SWITCH_TO_MODULE_WITH_TIMEOUT(module, timeout) do {\ - KTRY_EKOS_GADGET(QTabWidget, toolsWidget); \ - toolsWidget->setCurrentWidget(module); \ - QTRY_COMPARE_WITH_TIMEOUT(toolsWidget->currentWidget(), module, timeout);} while (false) +#include "test_ekos_capture_helper.h" #define QTEST_KSTARS_MAIN_GUIDERSELECT(klass) \ diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp index 68b56fb6ffb20f4e491a49de4f04ee8a209375ad..7771b9ac4f270d650b9bd6fb4275e42e16c04c85 100644 --- a/kstars/ekos/capture/capture.cpp +++ b/kstars/ekos/capture/capture.cpp @@ -665,7 +665,7 @@ void Capture::start() }); KSMessageBox::Instance()->questionYesNo(i18n("Are you imaging with %1 using your primary telescope?", - currentCCD->getDeviceName()), + currentCCD->getDeviceName()), i18n("Telescope Type"), 10, true); } else diff --git a/kstars/ekos/capture/capture.h b/kstars/ekos/capture/capture.h index e28f086f1c4d59018138686e1cc0d6a3286f8356..6ef105a0927038e2d9077f732de00ace7662e0d8 100644 --- a/kstars/ekos/capture/capture.h +++ b/kstars/ekos/capture/capture.h @@ -175,6 +175,12 @@ class Capture : public QWidget, public Ui::Capture */ Q_SCRIPTABLE bool loadSequenceQueue(const QString &fileURL); + /** DBUS interface function. + * Saves the Sequence Queue to the Ekos Sequence Queue file. + * @param fileURL full URL of the filename + */ + Q_SCRIPTABLE bool saveSequenceQueue(const QString &path); + /** DBUS interface function. * Enables or disables the maximum guiding deviation and sets its value. * @param enable If true, enable the guiding deviation check, otherwise, disable it. @@ -814,7 +820,6 @@ class Capture : public QWidget, public Ui::Capture void syncGUIToJob(SequenceJob *job); bool processJobInfo(XMLEle *root); void processJobCompletion(); - bool saveSequenceQueue(const QString &path); void constructPrefix(QString &imagePrefix); double setCurrentADU(double value); void llsq(QVector x, QVector y, double &a, double &b); diff --git a/kstars/ekos/manager.h b/kstars/ekos/manager.h index b1f6ab087c9592d2a425b6d12b604ee1fd432e64..daaebcc0fb65009137b1a2432e89fd4a21cd61f8 100644 --- a/kstars/ekos/manager.h +++ b/kstars/ekos/manager.h @@ -103,6 +103,10 @@ class Manager : public QDialog, public Ui::Manager } void addObjectToScheduler(SkyObject *object); + Scheduler *schedulerModule() + { + return schedulerProcess.get(); + } Guide *guideModule() { return guideProcess.get(); diff --git a/kstars/ekos/scheduler/scheduler.cpp b/kstars/ekos/scheduler/scheduler.cpp index 36f2742468fa0928553a2e07d117e94ffcbcc748..2b4f3d7b9c07829f2da1b470d400a767b40a79af 100644 --- a/kstars/ekos/scheduler/scheduler.cpp +++ b/kstars/ekos/scheduler/scheduler.cpp @@ -551,13 +551,12 @@ void Scheduler::processFITSSelection() } } -void Scheduler::selectSequence() +void Scheduler::setSequence(const QString &sequenceFileURL) { - sequenceURL = - QFileDialog::getOpenFileUrl(this, i18n("Select Sequence Queue"), dirPath, i18n("Ekos Sequence Queue (*.esq)")); - if (sequenceURL.isEmpty()) - return; + sequenceURL = QUrl::fromLocalFile(sequenceFileURL); + if (sequenceFileURL.isEmpty()) + return; dirPath = QUrl(sequenceURL.url(QUrl::RemoveFilename)); sequenceEdit->setText(sequenceURL.toLocalFile()); @@ -574,6 +573,13 @@ void Scheduler::selectSequence() setDirty(); } +void Scheduler::selectSequence() +{ + QString file = QFileDialog::getOpenFileName(this, i18n("Select Sequence Queue"), dirPath.toLocalFile(), i18n("Ekos Sequence Queue (*.esq)")); + + setSequence(file); +} + void Scheduler::selectStartupScript() { startupScriptURL = QFileDialog::getOpenFileUrl(this, i18n("Select Startup Script"), dirPath, i18n("Script (*)")); @@ -3720,6 +3726,18 @@ void Scheduler::load() startJobEvaluation(); } +void Scheduler::removeAllJobs() +{ + if (jobUnderEdit >= 0) + resetJobEdit(); + + while (queueTable->rowCount() > 0) + queueTable->removeRow(0); + + qDeleteAll(jobs); + jobs.clear(); +} + bool Scheduler::loadScheduler(const QString &fileURL) { SchedulerState const old_state = state; @@ -3736,14 +3754,7 @@ bool Scheduler::loadScheduler(const QString &fileURL) return false; } - if (jobUnderEdit >= 0) - resetJobEdit(); - - while (queueTable->rowCount() > 0) - queueTable->removeRow(0); - - qDeleteAll(jobs); - jobs.clear(); + removeAllJobs(); LilXML *xmlParser = newLilXML(); char errmsg[MAXRBUF]; @@ -4940,13 +4951,41 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) SchedulerJob::CapturedFramesMap capture_map; bool const rememberJobProgress = Options::rememberJobProgress(); - int totalSequenceCount = 0, totalCompletedCount = 0; + int totalCompletedCount = 0; double totalImagingTime = 0; // Determine number of captures in the scheduler job int capturesPerRepeat = 0; + QMap expected; foreach (SequenceJob *seqJob, seqJobs) + { capturesPerRepeat += seqJob->getCount(); + QString const signature = seqJob->getSignature(); + expected[signature] = seqJob->getCount() + (expected.contains(signature) ? expected[signature] : 0); + } + + // fill the captured frames map + for (QString key: expected.keys()) + { + if (rememberJobProgress) + { + int diff = expected[key] * schedJob->getRepeatsRequired() - capturedFramesCount[key]; + // captured more than required? + if (diff <= 0) + capture_map[key] = expected[key]; + // need more frames than one cycle could capture? + else if (diff >= expected[key]) + capture_map[key] = 0; + // else we know that 0 < diff < expected[key] + else + capture_map[key] = expected[key] - diff; + } + else + capture_map[key] = 0; + + // collect all captured frames counts + totalCompletedCount += capturedFramesCount[key]; + } // Loop through sequence jobs to calculate the number of required frames and estimate duration. foreach (SequenceJob *seqJob, seqJobs) @@ -4965,10 +5004,12 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) } // Note that looping jobs will have zero repeats required. - int const captures_required = seqJob->getCount() * schedJob->getRepeatsRequired(); + QString const signature = seqJob->getSignature(); + QString const signature_path = QFileInfo(signature).path(); + int captures_required = seqJob->getCount(); + int captures_completed = capturedFramesCount[signature]; - int captures_completed = 0; - if (rememberJobProgress) + if (rememberJobProgress && schedJob->getCompletionCondition() != SchedulerJob::FINISH_LOOP) { /* Enumerate sequence jobs associated to this scheduler job, and assign them a completed count. * @@ -4998,13 +5039,11 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) * This is why it is important to manage the repeat count of the scheduler job, as stated earlier. */ - // Retrieve cached count of completed captures for the output folder of this seqJob - QString const signature = seqJob->getSignature(); - QString const signature_path = QFileInfo(signature).path(); - captures_completed = capturedFramesCount[signature]; + // we start with the total value + captures_required = expected[seqJob->getSignature()] * schedJob->getRepeatsRequired(); qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 sees %2 captures in output folder '%3'.").arg(seqName).arg( - captures_completed).arg(signature_path); + captures_completed).arg(QFileInfo(signature).path()); // Enumerate sequence jobs to check how many captures are completed overall in the same storage as the current one foreach (SequenceJob *prevSeqJob, seqJobs) @@ -5014,50 +5053,27 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) break; // If the previous sequence signature matches the current, reduce completion count to take duplicates into account - if (!signature.compare(prevSeqJob->getLocalDir() + prevSeqJob->getDirectoryPostfix())) + if (!signature.compare(prevSeqJob->getSignature())) { // Note that looping jobs will have zero repeats required. - int const previous_captures_required = prevSeqJob->getCount() * schedJob->getRepeatsRequired(); + int const previous_captures_required = prevSeqJob->getCount(); qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has a previous duplicate sequence job requiring %2 captures.").arg( seqName).arg(previous_captures_required); - captures_completed -= previous_captures_required; + captures_required -= previous_captures_required; } - // Now completed count can be needlessly negative for this job, so clamp to zero - if (captures_completed < 0) - captures_completed = 0; + // Now required count can be needlessly negative for this job, so clamp to zero + if (captures_required < 0) + captures_required = 0; - // And break if no captures remain, this job has to execute - if (captures_completed == 0) + // And break if no captures remain, this job does not need to be executed + if (captures_required == 0) break; } - // Finally we're only interested in the number of captures required for this sequence item - if (0 < captures_required && captures_required < captures_completed) - captures_completed = captures_required; - qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has completed %2/%3 of its required captures in output folder '%4'.").arg( seqName).arg(captures_completed).arg(captures_required).arg(signature_path); - // Update the completion count for this signature in the frame map if we still have captures to take. - // That frame map will be transferred to the Capture module, for which the sequence is a single batch of the scheduler job. - // For instance, consider a scheduler job repeated 3 times and using a 3xLum sequence, so we want 9xLum in the end. - // - If no captures are already processed, the frame map contains Lum=0 - // - If 1xLum are already processed, the frame map contains Lum=0 when the batch executes, so that 3xLum may be taken. - // - If 3xLum are already processed, the frame map contains Lum=0 when the batch executes, as we still need more than what the sequence provides. - // - If 7xLum are already processed, the frame map contains Lum=1 when the batch executes, because we now only need 2xLum to finish the job. - // Therefore we need to specify a number of existing captures only for the last batch of the scheduler job. - // In the last batch, we only need the remainder of frames to get to the required total. - if (captures_completed < captures_required) - { - if (captures_required - captures_completed < seqJob->getCount()) - capture_map[signature] = captures_completed % seqJob->getCount(); - else - capture_map[signature] = 0; - } - else capture_map[signature] = captures_required; - - // From now on, 'captures_completed' is the number of frames completed for the *current* sequence job } // Else rely on the captures done during this session else @@ -5083,9 +5099,6 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 captures calibration frames.").arg(seqName); } - totalSequenceCount += captures_required; - totalCompletedCount += captures_completed; - /* If captures are not complete, we have imaging time left */ if (!areJobCapturesComplete) { @@ -5115,7 +5128,7 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) } schedJob->setCapturedFramesMap(capture_map); - schedJob->setSequenceCount(totalSequenceCount); + schedJob->setSequenceCount(capturesPerRepeat * schedJob->getRepeatsRequired()); // only in case we remember the job progress, we change the completion count if (rememberJobProgress) @@ -5165,7 +5178,7 @@ bool Scheduler::estimateJobTime(SchedulerJob *schedJob) schedJob->setEstimatedTime(0); qCDebug(KSTARS_EKOS_SCHEDULER) << QString("Job '%1' will not run, complete with %2/%3 captures.") - .arg(schedJob->getName()).arg(totalCompletedCount).arg(totalSequenceCount); + .arg(schedJob->getName()).arg(schedJob->getCompletedCount()).arg(schedJob->getSequenceCount()); } // Else consolidate with step durations else diff --git a/kstars/ekos/scheduler/scheduler.h b/kstars/ekos/scheduler/scheduler.h index 6bd34646e4709dae020f358bcfb558176f3a1c2a..912e0a4ba6a5cae9c4967d3cdae2b9d0d4dca8c9 100644 --- a/kstars/ekos/scheduler/scheduler.h +++ b/kstars/ekos/scheduler/scheduler.h @@ -203,6 +203,11 @@ class Scheduler : public QWidget, public Ui::Scheduler */ Q_SCRIPTABLE Q_NOREPLY void stop(); + /** DBUS interface function. + * @brief Remove all scheduler jobs + */ + Q_SCRIPTABLE Q_NOREPLY void removeAllJobs(); + /** DBUS interface function. * @brief Loads the Ekos Scheduler List (.esl) file. * @param fileURL path to a file @@ -210,6 +215,12 @@ class Scheduler : public QWidget, public Ui::Scheduler */ Q_SCRIPTABLE bool loadScheduler(const QString &fileURL); + /** DBUS interface function. + * @brief Set the file URL pointing to the capture sequence file + * @param sequenceFileURL URL of the capture sequence file + */ + Q_SCRIPTABLE void setSequence(const QString &sequenceFileURL); + /** DBUS interface function. * @brief Resets all jobs to IDLE */ @@ -244,10 +255,12 @@ class Scheduler : public QWidget, public Ui::Scheduler */ void setErrorHandlingStrategy (ErrorHandlingStrategy strategy); + /** @}*/ /** @{ */ - private: + +private: /** @internal Safeguard flag to avoid registering signals from widgets multiple times. */ bool jobChangesAreWatched { false }; diff --git a/kstars/org.kde.kstars.Ekos.Capture.xml b/kstars/org.kde.kstars.Ekos.Capture.xml index 8f3c908b21b518b1475c6565e7b071a3f81243c2..1f092a50ef1e618bda1b6c0b4621e6cb6497c1ea 100644 --- a/kstars/org.kde.kstars.Ekos.Capture.xml +++ b/kstars/org.kde.kstars.Ekos.Capture.xml @@ -41,6 +41,10 @@ + + + + diff --git a/kstars/org.kde.kstars.Ekos.Scheduler.xml b/kstars/org.kde.kstars.Ekos.Scheduler.xml index feb788bcc2ac65d9eeffa7b07231d632121e76d1..621f0dfd5b3ecf8ff542b7f061f706201483bdae 100644 --- a/kstars/org.kde.kstars.Ekos.Scheduler.xml +++ b/kstars/org.kde.kstars.Ekos.Scheduler.xml @@ -12,11 +12,19 @@ + + + + + + + +