File indexing completed on 2024-04-21 14:47:21

0001 /*  KStars UI tests
0002     SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 
0008 #include "test_ekos_capture.h"
0009 
0010 #if defined(HAVE_INDI)
0011 
0012 #include "Options.h"
0013 #include "kstars_ui_tests.h"
0014 #include "test_ekos.h"
0015 #include "test_ekos_simulator.h"
0016 #include "ekos/capture/capture.h"
0017 
0018 TestEkosCapture::TestEkosCapture(QObject *parent) : QObject(parent)
0019 {
0020 }
0021 
0022 void TestEkosCapture::initTestCase()
0023 {
0024     KVERIFY_EKOS_IS_HIDDEN();
0025     KTRY_OPEN_EKOS();
0026     KVERIFY_EKOS_IS_OPENED();
0027     KTRY_EKOS_START_SIMULATORS();
0028 
0029     // HACK: Reset clock to initial conditions
0030     KHACK_RESET_EKOS_TIME();
0031 }
0032 
0033 void TestEkosCapture::cleanupTestCase()
0034 {
0035     KTRY_EKOS_STOP_SIMULATORS();
0036     KTRY_CLOSE_EKOS();
0037     KVERIFY_EKOS_IS_HIDDEN();
0038 }
0039 
0040 void TestEkosCapture::init()
0041 {
0042     Ekos::Manager * const ekos = Ekos::Manager::Instance();
0043 
0044     // Wait for Capture to come up, switch to Capture tab
0045     QTRY_VERIFY_WITH_TIMEOUT(ekos->captureModule() != nullptr, 5000);
0046     KTRY_EKOS_GADGET(QTabWidget, toolsWidget);
0047     toolsWidget->setCurrentWidget(ekos->captureModule());
0048     QTRY_COMPARE_WITH_TIMEOUT(toolsWidget->currentWidget(), ekos->captureModule(), 1000);
0049     // wait 0.5 sec to ensure that the capture module has been initialized
0050     QTest::qWait(500);
0051 }
0052 
0053 void TestEkosCapture::cleanup()
0054 {
0055     Ekos::Manager::Instance()->captureModule()->clearSequenceQueue();
0056     KTRY_CAPTURE_GADGET(QTableWidget, queueTable);
0057     QTRY_VERIFY_WITH_TIMEOUT(queueTable->rowCount() == 0, 2000);
0058 }
0059 
0060 
0061 void TestEkosCapture::testAddCaptureJob()
0062 {
0063     KTRY_CAPTURE_GADGET(QDoubleSpinBox, captureExposureN);
0064     KTRY_CAPTURE_GADGET(QSpinBox, captureCountN);
0065     KTRY_CAPTURE_GADGET(QSpinBox, captureDelayN);
0066     KTRY_CAPTURE_GADGET(QComboBox, FilterPosCombo);
0067     KTRY_CAPTURE_GADGET(QComboBox, captureTypeS);
0068     KTRY_CAPTURE_GADGET(QPushButton, addToQueueB);
0069     KTRY_CAPTURE_GADGET(QTableWidget, queueTable);
0070 
0071     // These are the expected exhaustive list of frame names
0072     QString frameTypes[] = {"Light", "Bias", "Dark", "Flat"};
0073     int const frameTypeCount = sizeof(frameTypes) / sizeof(frameTypes[0]);
0074 
0075     // Verify our assumption about those frame types is correct
0076     QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->count(), frameTypeCount, 5000);
0077     for (QString &frameType : frameTypes)
0078         if(captureTypeS->findText(frameType) < 0)
0079             QFAIL(qPrintable(QString("Frame '%1' expected by the test is not in the Capture frame list").arg(frameType)));
0080 
0081     // These are the expected exhaustive list of filter names from the default Filter Wheel Simulator
0082     QString filterTypes[] = {"Red", "Green", "Blue", "H_Alpha", "OIII", "SII", "LPR", "Luminance"};
0083     int const filterTypeCount = sizeof(filterTypes) / sizeof(filterTypes[0]);
0084 
0085     // Verify our assumption about those filters is correct - but wait for properties to be read from the device
0086     QTRY_COMPARE_WITH_TIMEOUT(FilterPosCombo->count(), filterTypeCount, 5000);
0087     for (QString &filterType : filterTypes)
0088         if(FilterPosCombo->findText(filterType) < 0)
0089             QFAIL(qPrintable(QString("Filter '%1' expected by the test is not in the Capture filter list").arg(filterType)));
0090 
0091     // Add a few capture jobs
0092     int const job_count = 50;
0093     QWARN("When clicking the 'Add' button, immediately starting to fill the next job overwrites the job being added.");
0094     for (int i = 0; i < job_count; i++)
0095     {
0096         captureExposureN->setValue(((double)i) / 10.0);
0097         captureCountN->setValue(i);
0098         captureDelayN->setValue(i);
0099         KTRY_CAPTURE_COMBO_SET(captureTypeS, frameTypes[i % frameTypeCount]);
0100         KTRY_CAPTURE_COMBO_SET(FilterPosCombo, filterTypes[i % filterTypeCount]);
0101         KTRY_CAPTURE_CLICK(addToQueueB);
0102         // Wait for the job to be added, else the next loop will overwrite the current job
0103         QTRY_COMPARE_WITH_TIMEOUT(queueTable->rowCount(), i + 1, 100);
0104     }
0105 
0106     // Count the number of rows
0107     QVERIFY(queueTable->rowCount() == job_count);
0108 
0109     // Check first capture job item, which could not accept exposure duration 0 and count 0
0110     QWARN("This test assumes that minimal exposure is 0.01 for the CCD Simulator.");
0111     queueTable->setCurrentCell(0, 1);
0112     QTRY_VERIFY_WITH_TIMEOUT(queueTable->currentRow() == 0, 1000);
0113 
0114     // It actually takes time before all signals syncing UI are processed, so wait for situation to settle
0115     QTRY_COMPARE_WITH_TIMEOUT(captureExposureN->value(), 0.01, 1000);
0116     QTRY_COMPARE_WITH_TIMEOUT(captureCountN->value(), 1, 1000);
0117     QTRY_COMPARE_WITH_TIMEOUT(captureDelayN->value(), 0, 1000);
0118     QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->currentText(), frameTypes[0], 1000);
0119     QTRY_COMPARE_WITH_TIMEOUT(FilterPosCombo->currentText(), filterTypes[0], 1000);
0120 
0121     // Select a few cells and verify the feedback on the left side UI
0122     srand(42);
0123     for (int index = 1; index < job_count / 2; index += rand() % 4 + 1)
0124     {
0125         QVERIFY(index < queueTable->rowCount());
0126         queueTable->setCurrentCell(index, 1);
0127         QTRY_VERIFY_WITH_TIMEOUT(queueTable->currentRow() == index, 1000);
0128 
0129         // It actually takes time before all signals syncing UI are processed, so wait for situation to settle
0130         QTRY_VERIFY_WITH_TIMEOUT(std::fabs(captureExposureN->value() - static_cast<double>(index) / 10.0) < 0.1, 1000);
0131         QTRY_COMPARE_WITH_TIMEOUT(captureCountN->value(), index, 1000);
0132         QTRY_COMPARE_WITH_TIMEOUT(captureDelayN->value(), index, 1000);
0133         QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->currentText(), frameTypes[index % frameTypeCount], 1000);
0134         QTRY_COMPARE_WITH_TIMEOUT(FilterPosCombo->currentText(), filterTypes[index % filterTypeCount], 1000);
0135     }
0136 
0137     // Remove all the rows
0138     // TODO: test edge cases with the selected row
0139     KTRY_CAPTURE_GADGET(QPushButton, removeFromQueueB);
0140     for (int i = job_count; 0 < i; i--)
0141     {
0142         KTRY_CAPTURE_CLICK(removeFromQueueB);
0143         QVERIFY(i - 1 == queueTable->rowCount());
0144     }
0145 }
0146 
0147 void TestEkosCapture::testCaptureToTemporary()
0148 {
0149     QTemporaryDir destination;
0150     QVERIFY(destination.isValid());
0151     QVERIFY(destination.autoRemove());
0152 
0153     // Add five exposures
0154     KTRY_CAPTURE_ADD_LIGHT(0.1, 5, 0, "Red", "test", destination.path());
0155 
0156     // Start capturing and wait for procedure to end (visual icon changing)
0157     KTRY_CAPTURE_GADGET(QPushButton, startB);
0158     QCOMPARE(startB->icon().name(), QString("media-playback-start"));
0159     KTRY_CAPTURE_CLICK(startB);
0160     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
0161     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 30000);
0162 
0163     QWARN("Test capturing to temporary is no longer valid since we don't create temporary files any more.");
0164     //    QWARN("When storing to a recognized system temporary folder, only one FITS file is created.");
0165     //    QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == 1, 1000);
0166     //    QCOMPARE(searchFITS(QDir(destination.path()))[0], QString("Light_005.fits"));
0167 }
0168 
0169 void TestEkosCapture::testCaptureSingle()
0170 {
0171     // We cannot use a system temporary due to what testCaptureToTemporary marks
0172     QTemporaryDir destination(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX");
0173     QVERIFY(destination.isValid());
0174     QVERIFY(destination.autoRemove());
0175 
0176     // Add an exposure
0177     KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 0, "Red", "test", destination.path());
0178 
0179     // Start capturing and wait for procedure to end (visual icon changing)
0180     KTRY_CAPTURE_GADGET(QPushButton, startB);
0181     QCOMPARE(startB->icon().name(), QString("media-playback-start"));
0182     KTRY_CAPTURE_CLICK(startB);
0183     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
0184     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 2000);
0185 
0186     // Verify a FITS file was created
0187     QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->searchFITS(QDir(destination.path())).count() == 1, 1000);
0188     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[0].startsWith("test_Light_"));
0189     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[0].endsWith("001.fits"));
0190 
0191     // Reset sequence state - this makes a confirmation dialog appear
0192     volatile bool dialogValidated = false;
0193     QTimer::singleShot(200, [&]
0194     {
0195         QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget());
0196         if(dialog != nullptr)
0197         {
0198             QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Yes), Qt::LeftButton);
0199             dialogValidated = true;
0200         }
0201     });
0202     KTRY_CAPTURE_CLICK(resetB);
0203     QTRY_VERIFY_WITH_TIMEOUT(dialogValidated, 1000);
0204 
0205     // Capture again
0206     QCOMPARE(startB->icon().name(), QString("media-playback-start"));
0207     KTRY_CAPTURE_CLICK(startB);
0208     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
0209     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 2000);
0210 
0211     // Verify an additional FITS file was created - asynchronously eventually
0212     QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->searchFITS(QDir(destination.path())).count() == 2, 2000);
0213     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[0].startsWith("test_Light_"));
0214     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[0].endsWith("001.fits"));
0215     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[1].startsWith("test_Light_"));
0216     QVERIFY(m_CaptureHelper->searchFITS(QDir(destination.path()))[1].endsWith("002.fits"));
0217 
0218     // TODO: test storage options
0219 }
0220 
0221 void TestEkosCapture::testCaptureMultiple()
0222 {
0223     // We cannot use a system temporary due to what testCaptureToTemporary marks
0224     QTemporaryDir destination(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX");
0225     QVERIFY(destination.isValid());
0226     QVERIFY(destination.autoRemove());
0227 
0228     // Add a few exposures
0229     KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 0, "Red", "test", destination.path() + "/%T");
0230     KTRY_CAPTURE_ADD_LIGHT(0.7, 2, 0, "SII", "test", destination.path() + "/%T");
0231     KTRY_CAPTURE_ADD_LIGHT(0.2, 5, 0, "Green", "test", destination.path() + "/%T");
0232     KTRY_CAPTURE_ADD_LIGHT(0.9, 2, 0, "Luminance", "test", destination.path() + "/%T");
0233     KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 1, "H_Alpha", "test", destination.path() + "/%T");
0234     QWARN("A sequence of exposures under 1 second will always take 1 second to capture each of them.");
0235     //size_t const duration = (500+0)*1+(700+0)*2+(200+0)*5+(900+0)*2+(500+1000)*1;
0236     size_t const duration = 1000 * (1 + 2 + 5 + 2 + 1);
0237     size_t const count = 1 + 2 + 5 + 2 + 1;
0238 
0239     // Start capturing and wait for procedure to end (visual icon changing) - leave enough time for frames to store
0240     KTRY_CAPTURE_GADGET(QPushButton, startB);
0241     QCOMPARE(startB->icon().name(), QString("media-playback-start"));
0242     KTRY_CAPTURE_CLICK(startB);
0243     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
0244     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), duration * 2);
0245 
0246     // Verify the proper number of FITS file were created
0247     QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->searchFITS(QDir(destination.path())).count() == count, 1000);
0248 
0249     // Reset sequence state - this makes a confirmation dialog appear
0250     volatile bool dialogValidated = false;
0251     QTimer::singleShot(200, [&]
0252     {
0253         QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget());
0254         if(dialog != nullptr)
0255         {
0256             QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Yes), Qt::LeftButton);
0257             dialogValidated = true;
0258         }
0259     });
0260     KTRY_CAPTURE_CLICK(resetB);
0261     QTRY_VERIFY_WITH_TIMEOUT(dialogValidated, 500);
0262 
0263     // Capture again
0264     KTRY_CAPTURE_CLICK(startB);
0265     QTRY_VERIFY_WITH_TIMEOUT(!startB->icon().name().compare("media-playback-stop"), 500);
0266     QTRY_VERIFY_WITH_TIMEOUT(!startB->icon().name().compare("media-playback-start"), duration * 2);
0267 
0268     // Verify the proper number of additional FITS file were created again
0269     QTRY_VERIFY_WITH_TIMEOUT(m_CaptureHelper->searchFITS(QDir(destination.path())).count() == 2 * count, 1000);
0270 
0271     // TODO: test storage options
0272 }
0273 
0274 void TestEkosCapture::testCaptureDarkFlats()
0275 {
0276     // ensure that we know that the CCD has a shutter
0277     m_CaptureHelper->ensureCCDShutter(true);
0278     // We cannot use a system temporary due to what testCaptureToTemporary marks
0279     QTemporaryDir destination(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX");
0280     QVERIFY(destination.isValid());
0281     QVERIFY(destination.autoRemove());
0282 
0283     KTRY_CAPTURE_GADGET(QTableWidget, queueTable);
0284 
0285     // Set to flat first so that the flat calibration button is enabled
0286     KTRY_CAPTURE_GADGET(QComboBox, captureTypeS);
0287     KTRY_CAPTURE_COMBO_SET(captureTypeS, "Flat");
0288 
0289     volatile bool dialogValidated = false;
0290     QTimer::singleShot(200, [&]
0291     {
0292         QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget());
0293         if(dialog != nullptr)
0294         {
0295             // Set Flat duration to ADU
0296             QRadioButton *ADUC = dialog->findChild<QRadioButton *>("ADUC");
0297             QVERIFY(ADUC);
0298             ADUC->setChecked(true);
0299 
0300             // Set ADU to 4000
0301             QSpinBox *ADUValue = dialog->findChild<QSpinBox *>("ADUValue");
0302             QVERIFY(ADUValue);
0303             ADUValue->setValue(4000);
0304 
0305 
0306             QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Ok), Qt::LeftButton);
0307             dialogValidated = true;
0308         }
0309     });
0310 
0311     // Toggle flat calibration dialog
0312     KTRY_CAPTURE_CLICK(calibrationB);
0313 
0314     QTRY_VERIFY_WITH_TIMEOUT(dialogValidated, 1000);
0315 
0316     // Add flats
0317     KTRY_CAPTURE_ADD_FLAT(1, 2, 0, "Red", destination.path());
0318     KTRY_CAPTURE_ADD_FLAT(0.1, 3, 0, "Green", destination.path());
0319 
0320     // Generate Dark Flats
0321     KTRY_CAPTURE_CLICK(generateDarkFlatsB);
0322 
0323     // We should have 4 jobs in total (2 flats and 2 dark flats)
0324     QTRY_VERIFY2_WITH_TIMEOUT(queueTable->rowCount() == 4,
0325                               QString("Row number wrong, %1 expected, %2 found!").arg(4).arg(queueTable->rowCount()).toStdString().c_str(), 1000);
0326 
0327     // Start capturing and wait for procedure to end (visual icon changing) - leave enough time for frames to store
0328     KTRY_CAPTURE_GADGET(QPushButton, startB);
0329     QCOMPARE(startB->icon().name(), QString("media-playback-start"));
0330     KTRY_CAPTURE_CLICK(startB);
0331     QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
0332 
0333     // Accept any dialogs
0334     QTimer::singleShot(5000, [&]
0335     {
0336         auto dialog = qobject_cast <QMessageBox*> (QApplication::activeModalWidget());
0337         if(dialog)
0338         {
0339             dialog->accept();
0340         }
0341     });
0342 
0343     QTRY_VERIFY_WITH_TIMEOUT(!startB->icon().name().compare("media-playback-start"), 120000);
0344 
0345     // Verify the proper number of FITS file were created
0346     QTRY_COMPARE_WITH_TIMEOUT(m_CaptureHelper->searchFITS(QDir(destination.path())).count(), 10, 1000);
0347 
0348     // Verify dark flat job (3) matches flat job (1) time
0349     queueTable->selectRow(0);
0350     KTRY_CAPTURE_GADGET(NonLinearDoubleSpinBox, captureExposureN);
0351     auto flatDuration = captureExposureN->value();
0352 
0353     queueTable->selectRow(2);
0354     auto darkDuration = captureExposureN->value();
0355 
0356     QTRY_COMPARE(darkDuration, flatDuration);
0357 
0358 
0359 }
0360 
0361 QTEST_KSTARS_MAIN(TestEkosCapture)
0362 
0363 #endif // HAVE_INDI