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