1 /* KStars UI tests
2 SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7
8 #include "test_ekos_capture.h"
9
10 #if defined(HAVE_INDI)
11
12 #include "kstars_ui_tests.h"
13 #include "test_ekos.h"
14 #include "test_ekos_simulator.h"
15
TestEkosCapture(QObject * parent)16 TestEkosCapture::TestEkosCapture(QObject *parent) : QObject(parent)
17 {
18 }
19
initTestCase()20 void TestEkosCapture::initTestCase()
21 {
22 KVERIFY_EKOS_IS_HIDDEN();
23 KTRY_OPEN_EKOS();
24 KVERIFY_EKOS_IS_OPENED();
25 KTRY_EKOS_START_SIMULATORS();
26
27 // HACK: Reset clock to initial conditions
28 KHACK_RESET_EKOS_TIME();
29 }
30
cleanupTestCase()31 void TestEkosCapture::cleanupTestCase()
32 {
33 KTRY_EKOS_STOP_SIMULATORS();
34 KTRY_CLOSE_EKOS();
35 KVERIFY_EKOS_IS_HIDDEN();
36 }
37
init()38 void TestEkosCapture::init()
39 {
40 Ekos::Manager * const ekos = Ekos::Manager::Instance();
41
42 // Wait for Capture to come up, switch to Focus tab
43 QTRY_VERIFY_WITH_TIMEOUT(ekos->captureModule() != nullptr, 5000);
44 KTRY_EKOS_GADGET(QTabWidget, toolsWidget);
45 toolsWidget->setCurrentWidget(ekos->captureModule());
46 QTRY_COMPARE_WITH_TIMEOUT(toolsWidget->currentWidget(), ekos->captureModule(), 1000);
47 }
48
cleanup()49 void TestEkosCapture::cleanup()
50 {
51 Ekos::Manager::Instance()->captureModule()->clearSequenceQueue();
52 KTRY_CAPTURE_GADGET(QTableWidget, queueTable);
53 QTRY_VERIFY_WITH_TIMEOUT(queueTable->rowCount() == 0, 2000);
54 }
55
searchFITS(QDir const & dir) const56 QStringList TestEkosCapture::searchFITS(QDir const &dir) const
57 {
58 QStringList list = dir.entryList(QDir::Files);
59
60 //foreach (auto &f, list)
61 // QWARN(QString(dir.path()+'/'+f).toStdString().c_str());
62
63 foreach (auto &d, dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs))
64 list.append(searchFITS(QDir(dir.path() + '/' + d)));
65
66 return list;
67 }
68
testAddCaptureJob()69 void TestEkosCapture::testAddCaptureJob()
70 {
71 KTRY_CAPTURE_GADGET(QDoubleSpinBox, captureExposureN);
72 KTRY_CAPTURE_GADGET(QSpinBox, captureCountN);
73 KTRY_CAPTURE_GADGET(QSpinBox, captureDelayN);
74 KTRY_CAPTURE_GADGET(QComboBox, captureFilterS);
75 KTRY_CAPTURE_GADGET(QComboBox, captureTypeS);
76 KTRY_CAPTURE_GADGET(QPushButton, addToQueueB);
77 KTRY_CAPTURE_GADGET(QTableWidget, queueTable);
78
79 // These are the expected exhaustive list of frame names
80 QString frameTypes[] = {"Light", "Bias", "Dark", "Flat"};
81 int const frameTypeCount = sizeof(frameTypes) / sizeof(frameTypes[0]);
82
83 // Verify our assumption about those frame types is correct
84 QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->count(), frameTypeCount, 5000);
85 for (QString &frameType: frameTypes)
86 if(captureTypeS->findText(frameType) < 0)
87 QFAIL(qPrintable(QString("Frame '%1' expected by the test is not in the Capture frame list").arg(frameType)));
88
89 // These are the expected exhaustive list of filter names from the default Filter Wheel Simulator
90 QString filterTypes[] = {"Red", "Green", "Blue", "H_Alpha", "OIII", "SII", "LPR", "Luminance"};
91 int const filterTypeCount = sizeof(filterTypes) / sizeof(filterTypes[0]);
92
93 // Verify our assumption about those filters is correct - but wait for properties to be read from the device
94 QTRY_COMPARE_WITH_TIMEOUT(captureFilterS->count(), filterTypeCount, 5000);
95 for (QString &filterType: filterTypes)
96 if(captureFilterS->findText(filterType) < 0)
97 QFAIL(qPrintable(QString("Filter '%1' expected by the test is not in the Capture filter list").arg(filterType)));
98
99 // Add a few capture jobs
100 int const job_count = 50;
101 QWARN("When clicking the 'Add' button, immediately starting to fill the next job overwrites the job being added.");
102 for (int i = 0; i < job_count; i++)
103 {
104 captureExposureN->setValue(((double)i) / 10.0);
105 captureCountN->setValue(i);
106 captureDelayN->setValue(i);
107 KTRY_CAPTURE_COMBO_SET(captureTypeS, frameTypes[i % frameTypeCount]);
108 KTRY_CAPTURE_COMBO_SET(captureFilterS, filterTypes[i % filterTypeCount]);
109 KTRY_CAPTURE_CLICK(addToQueueB);
110 // Wait for the job to be added, else the next loop will overwrite the current job
111 QTRY_COMPARE_WITH_TIMEOUT(queueTable->rowCount(), i + 1, 100);
112 }
113
114 // Count the number of rows
115 QVERIFY(queueTable->rowCount() == job_count);
116
117 // Check first capture job item, which could not accept exposure duration 0 and count 0
118 QWARN("This test assumes that minimal exposure is 0.01 for the CCD Simulator.");
119 queueTable->setCurrentCell(0, 1);
120 QTRY_VERIFY_WITH_TIMEOUT(queueTable->currentRow() == 0, 1000);
121
122 // It actually takes time before all signals syncing UI are processed, so wait for situation to settle
123 QTRY_COMPARE_WITH_TIMEOUT(captureExposureN->value(), 0.01, 1000);
124 QTRY_COMPARE_WITH_TIMEOUT(captureCountN->value(), 1, 1000);
125 QTRY_COMPARE_WITH_TIMEOUT(captureDelayN->value(), 0, 1000);
126 QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->currentText(), frameTypes[0], 1000);
127 QTRY_COMPARE_WITH_TIMEOUT(captureFilterS->currentText(), filterTypes[0], 1000);
128
129 // Select a few cells and verify the feedback on the left side UI
130 srand(42);
131 for (int index = 1; index < job_count / 2; index += rand() % 4 + 1)
132 {
133 QVERIFY(index < queueTable->rowCount());
134 queueTable->setCurrentCell(index, 1);
135 QTRY_VERIFY_WITH_TIMEOUT(queueTable->currentRow() == index, 1000);
136
137 // It actually takes time before all signals syncing UI are processed, so wait for situation to settle
138 QTRY_VERIFY_WITH_TIMEOUT(std::fabs(captureExposureN->value() - static_cast<double>(index) / 10.0) < 0.1, 1000);
139 QTRY_COMPARE_WITH_TIMEOUT(captureCountN->value(), index, 1000);
140 QTRY_COMPARE_WITH_TIMEOUT(captureDelayN->value(), index, 1000);
141 QTRY_COMPARE_WITH_TIMEOUT(captureTypeS->currentText(), frameTypes[index % frameTypeCount], 1000);
142 QTRY_COMPARE_WITH_TIMEOUT(captureFilterS->currentText(), filterTypes[index % filterTypeCount], 1000);
143 }
144
145 // Remove all the rows
146 // TODO: test edge cases with the selected row
147 KTRY_CAPTURE_GADGET(QPushButton, removeFromQueueB);
148 for (int i = job_count; 0 < i; i--)
149 {
150 KTRY_CAPTURE_CLICK(removeFromQueueB);
151 QVERIFY(i - 1 == queueTable->rowCount());
152 }
153 }
154
testCaptureToTemporary()155 void TestEkosCapture::testCaptureToTemporary()
156 {
157 QTemporaryDir destination;
158 QVERIFY(destination.isValid());
159 QVERIFY(destination.autoRemove());
160
161 // Add five exposures
162 KTRY_CAPTURE_ADD_LIGHT(0.1, 5, 0, "Red", destination.path());
163
164 // Start capturing and wait for procedure to end (visual icon changing)
165 KTRY_CAPTURE_GADGET(QPushButton, startB);
166 QCOMPARE(startB->icon().name(), QString("media-playback-start"));
167 KTRY_CAPTURE_CLICK(startB);
168 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
169 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 30000);
170
171 QWARN("Test capturing to temporary is no longer valid since we don't create temporary files any more.");
172 // QWARN("When storing to a recognized system temporary folder, only one FITS file is created.");
173 // QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == 1, 1000);
174 // QCOMPARE(searchFITS(QDir(destination.path()))[0], QString("Light_005.fits"));
175 }
176
testCaptureSingle()177 void TestEkosCapture::testCaptureSingle()
178 {
179 // We cannot use a system temporary due to what testCaptureToTemporary marks
180 QTemporaryDir destination(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX");
181 QVERIFY(destination.isValid());
182 QVERIFY(destination.autoRemove());
183
184 // Add an exposure
185 KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 0, "Red", destination.path());
186
187 // Start capturing and wait for procedure to end (visual icon changing)
188 KTRY_CAPTURE_GADGET(QPushButton, startB);
189 QCOMPARE(startB->icon().name(), QString("media-playback-start"));
190 KTRY_CAPTURE_CLICK(startB);
191 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
192 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 2000);
193
194 // Verify a FITS file was created
195 QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == 1, 1000);
196 QVERIFY(searchFITS(QDir(destination.path()))[0].startsWith("Light_"));
197 QVERIFY(searchFITS(QDir(destination.path()))[0].endsWith("_001.fits"));
198
199 // Reset sequence state - this makes a confirmation dialog appear
200 volatile bool dialogValidated = false;
201 QTimer::singleShot(200, [&]
202 {
203 QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget());
204 if(dialog != nullptr)
205 {
206 QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Yes), Qt::LeftButton);
207 dialogValidated = true;
208 }
209 });
210 KTRY_CAPTURE_CLICK(resetB);
211 QTRY_VERIFY_WITH_TIMEOUT(dialogValidated, 1000);
212
213 // Capture again
214 QCOMPARE(startB->icon().name(), QString("media-playback-start"));
215 KTRY_CAPTURE_CLICK(startB);
216 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
217 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), 2000);
218
219 // Verify an additional FITS file was created - asynchronously eventually
220 QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == 2, 2000);
221 QVERIFY(searchFITS(QDir(destination.path()))[0].startsWith("Light_"));
222 QVERIFY(searchFITS(QDir(destination.path()))[0].endsWith("_001.fits"));
223 QVERIFY(searchFITS(QDir(destination.path()))[1].startsWith("Light_"));
224 QVERIFY(searchFITS(QDir(destination.path()))[1].endsWith("_002.fits"));
225
226 // TODO: test storage options
227 }
228
testCaptureMultiple()229 void TestEkosCapture::testCaptureMultiple()
230 {
231 // We cannot use a system temporary due to what testCaptureToTemporary marks
232 QTemporaryDir destination(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/test-XXXXXX");
233 QVERIFY(destination.isValid());
234 QVERIFY(destination.autoRemove());
235
236 // Add a few exposures
237 KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 0, "Red", destination.path());
238 KTRY_CAPTURE_ADD_LIGHT(0.7, 2, 0, "SII", destination.path());
239 KTRY_CAPTURE_ADD_LIGHT(0.2, 5, 0, "Green", destination.path());
240 KTRY_CAPTURE_ADD_LIGHT(0.9, 2, 0, "Luminance", destination.path());
241 KTRY_CAPTURE_ADD_LIGHT(0.5, 1, 1, "H_Alpha", destination.path());
242 QWARN("A sequence of exposures under 1 second will always take 1 second to capture each of them.");
243 //size_t const duration = (500+0)*1+(700+0)*2+(200+0)*5+(900+0)*2+(500+1000)*1;
244 size_t const duration = 1000 * (1 + 2 + 5 + 2 + 1);
245 size_t const count = 1 + 2 + 5 + 2 + 1;
246
247 // Start capturing and wait for procedure to end (visual icon changing) - leave enough time for frames to store
248 KTRY_CAPTURE_GADGET(QPushButton, startB);
249 QCOMPARE(startB->icon().name(), QString("media-playback-start"));
250 KTRY_CAPTURE_CLICK(startB);
251 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-stop"), 500);
252 QTRY_COMPARE_WITH_TIMEOUT(startB->icon().name(), QString("media-playback-start"), duration * 2);
253
254 // Verify the proper number of FITS file were created
255 QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == count, 1000);
256
257 // Reset sequence state - this makes a confirmation dialog appear
258 volatile bool dialogValidated = false;
259 QTimer::singleShot(200, [&]
260 {
261 QDialog * const dialog = qobject_cast <QDialog*> (QApplication::activeModalWidget());
262 if(dialog != nullptr)
263 {
264 QTest::mouseClick(dialog->findChild<QDialogButtonBox*>()->button(QDialogButtonBox::Yes), Qt::LeftButton);
265 dialogValidated = true;
266 }
267 });
268 KTRY_CAPTURE_CLICK(resetB);
269 QTRY_VERIFY_WITH_TIMEOUT(dialogValidated, 500);
270
271 // Capture again
272 KTRY_CAPTURE_CLICK(startB);
273 QTRY_VERIFY_WITH_TIMEOUT(!startB->icon().name().compare("media-playback-stop"), 500);
274 QTRY_VERIFY_WITH_TIMEOUT(!startB->icon().name().compare("media-playback-start"), duration * 2);
275
276 // Verify the proper number of additional FITS file were created again
277 QTRY_VERIFY_WITH_TIMEOUT(searchFITS(QDir(destination.path())).count() == 2 * count, 1000);
278
279 // TODO: test storage options
280 }
281
282 QTEST_KSTARS_MAIN(TestEkosCapture)
283
284 #endif // HAVE_INDI
285