1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2017 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include <core/GUITest.h>
23 
24 #include <QApplication>
25 #include <QDateTime>
26 #include <QMessageBox>
27 #include <QProcess>
28 #include <QPushButton>
29 #include <QTimer>
30 
31 #include "GTUtilsDialog.h"
32 #include "drivers/GTMouseDriver.h"
33 #include "primitives/GTWidget.h"
34 #include "utils/GTThread.h"
35 #include "utils/GTUtilsMac.h"
36 
37 namespace HI {
38 
39 #define GT_CLASS_NAME "GUIDialogWaiter"
40 
GUIDialogWaiter(GUITestOpStatus & _os,Runnable * _r,const WaitSettings & _settings)41 GUIDialogWaiter::GUIDialogWaiter(GUITestOpStatus &_os, Runnable *_r, const WaitSettings &_settings)
42     : isFinished(false), waiterId(-1), os(_os), runnable(_r), settings(_settings), timer(NULL), waitingTime(0) {
43     static int totalWaiterCount = 0;
44     waiterId = totalWaiterCount++;
45 
46     timer = new QTimer();
47 
48     timer->connect(timer, SIGNAL(timeout()), this, SLOT(checkDialog()));
49     timer->start(timerPeriod);
50 }
51 
~GUIDialogWaiter()52 GUIDialogWaiter::~GUIDialogWaiter() {
53     finishWaiting();
54 }
55 
finishWaiting()56 void GUIDialogWaiter::finishWaiting() {
57     delete timer;
58     timer = nullptr;
59     delete runnable;
60     runnable = nullptr;
61 }
62 
stopTimer()63 void GUIDialogWaiter::stopTimer() {
64     if (timer != nullptr) {
65         timer->stop();
66     }
67 }
68 
isExpectedName(const QString & widgetObjectName,const QString & expectedObjectName)69 bool GUIDialogWaiter::isExpectedName(const QString &widgetObjectName, const QString &expectedObjectName) {
70     if (expectedObjectName.isNull()) {
71         qWarning("GT_DEBUG_MESSAGE GUIDialogWaiter Warning!! Checking name, widget name '%s', but expected any, saying it's expected", widgetObjectName.toLocal8Bit().constData());
72         return true;
73     }
74 
75     qDebug("GT_DEBUG_MESSAGE GUIDialogWaiter Checking name, widget name '%s', expected '%s'", widgetObjectName.toLocal8Bit().constData(), expectedObjectName.toLocal8Bit().constData());
76     return widgetObjectName == expectedObjectName;
77 }
78 
79 #define GT_METHOD_NAME "checkDialog"
checkDialog()80 void GUIDialogWaiter::checkDialog() {
81     try {
82         QWidget *widget = NULL;
83         GT_CHECK_NO_MESSAGE(runnable != NULL, "Runnable is NULL");
84 
85         switch (settings.dialogType) {
86             case Modal:
87                 widget = QApplication::activeModalWidget();
88                 break;
89             case Popup:
90                 widget = QApplication::activePopupWidget();
91                 break;
92             default:
93                 break;
94         }
95 
96         if (widget != nullptr && !isFinished && isExpectedName(widget->objectName(), settings.objectName)) {
97             timer->stop();
98             qDebug("-------------------------");
99             qDebug("GT_DEBUG_MESSAGE GUIDialogWaiter::wait ID = %d, name = '%s' going to RUN", waiterId, settings.objectName.toLocal8Bit().constData());
100             qDebug("-------------------------");
101 
102             GT_CHECK(settings.destiny != MustNotBeRun,
103                      QString("Dialog appears which mustn't appear: %1")
104                          .arg(settings.objectName.isEmpty() ? "(an unnamed dialog)" : settings.objectName));
105 
106             try {
107                 GTThread::waitForMainThread();
108                 runnable->run();
109                 isFinished = true;
110             } catch (GUITestOpStatus *) {
111                 QWidget *popupWidget = QApplication::activePopupWidget();
112                 while (popupWidget != NULL) {
113                     GTWidget::close(os, popupWidget);
114                     popupWidget = QApplication::activePopupWidget();
115                 }
116 
117                 QWidget *modalWidget = QApplication::activeModalWidget();
118                 while (modalWidget != NULL) {
119                     GTWidget::close(os, modalWidget);
120                     modalWidget = QApplication::activeModalWidget();
121                 }
122             }
123         } else {
124             waitingTime += timerPeriod;
125             if (waitingTime > settings.timeout) {
126                 qDebug("-------------------------");
127                 qDebug("GT_DEBUG_MESSAGE !!! GUIDialogWaiter::TIMEOUT Id = %d, going to finish waiting", waiterId);
128                 qDebug("-------------------------");
129 
130                 finishWaiting();
131                 GT_CHECK(false, "TIMEOUT, waiterId = " + QString::number(waiterId));
132             }
133         }
134 
135     } catch (GUITestOpStatus *) {
136     }
137 }
138 #undef GT_METHOD_NAME
139 
140 #undef GT_CLASS_NAME
141 
142 #define GT_CLASS_NAME "GTUtilsDialog"
143 
HangChecker(GUITestOpStatus & _os)144 HangChecker::HangChecker(GUITestOpStatus &_os)
145     : os(_os), mightHung(false) {
146     timer = new QTimer();
147 }
148 
startChecking()149 void HangChecker::startChecking() {
150     timer->connect(timer, SIGNAL(timeout()), this, SLOT(sl_check()));
151     timer->start(GTUtilsDialog::timerPeriod * 100);
152 }
153 
154 #define GT_METHOD_NAME "sl_check"
sl_check()155 void HangChecker::sl_check() {
156     QWidget *dialog = QApplication::activeModalWidget();
157     try {
158         if (dialog != NULL) {
159             bool found = false;
160             foreach (GUIDialogWaiter *waiter, GTUtilsDialog::pool) {
161                 if (!waiter->isFinished && waiter->isExpectedName(dialog->objectName(), waiter->getSettings().objectName)) {
162                     found = true;
163                     mightHung = false;
164                 }
165             }
166 
167             if (!found) {
168                 if (mightHung) {
169                     GT_CHECK(false, "dialog " + QString(dialog->metaObject()->className()) + " name: " + dialog->objectName() + " hang up");
170                 }
171             }
172 
173             if (!found) {
174                 if (!mightHung) {
175                     mightHung = true;
176                     qWarning("GT_DEBUG_MESSAGE dialog mignt hang up");
177                 }
178             }
179 
180         } else {
181             mightHung = false;
182         }
183     } catch (GUITestOpStatus *) {
184         GTGlobals::takeScreenShot(GUITest::screenshotDir + QDateTime::currentDateTime().toString() + ".jpg");
185         QWidget *w = QApplication::activeModalWidget();
186         while (w != NULL) {
187             w->close();
188             w = QApplication::activeModalWidget();
189         }
190         w = QApplication::activePopupWidget();
191         while (w != NULL) {
192             w->close();
193             w = QApplication::activePopupWidget();
194         }
195     }
196 }
197 #undef GT_METHOD_NAME
198 
199 #undef GT_CLASS_NAME
200 
201 #define GT_CLASS_NAME "GTUtilsDialog"
202 
203 QList<GUIDialogWaiter *> GTUtilsDialog::pool = QList<GUIDialogWaiter *>();
204 HangChecker *GTUtilsDialog::hangChecker = NULL;
205 
startHangChecking(GUITestOpStatus & os)206 void GTUtilsDialog::startHangChecking(GUITestOpStatus &os) {
207     hangChecker = new HangChecker(os);
208     hangChecker->startChecking();
209 }
210 
stopHangChecking()211 void GTUtilsDialog::stopHangChecking() {
212     if (hangChecker != NULL) {
213         hangChecker->timer->stop();
214     }
215 }
216 
217 #define GT_METHOD_NAME "buttonBox"
buttonBox(GUITestOpStatus & os,QWidget * dialog)218 QDialogButtonBox *GTUtilsDialog::buttonBox(GUITestOpStatus &os, QWidget *dialog) {
219     return qobject_cast<QDialogButtonBox *>(GTWidget::findWidget(os, "buttonBox", dialog));
220 }
221 #undef GT_METHOD_NAME
222 
223 #define GT_METHOD_NAME "clickButtonBox"
clickButtonBox(GUITestOpStatus & os,QDialogButtonBox::StandardButton button)224 void GTUtilsDialog::clickButtonBox(GUITestOpStatus &os, QDialogButtonBox::StandardButton button) {
225     clickButtonBox(os, QApplication::activeModalWidget(), button);
226 }
227 #undef GT_METHOD_NAME
228 
229 #define GT_METHOD_NAME "clickButtonBox"
clickButtonBox(GUITestOpStatus & os,QWidget * dialog,QDialogButtonBox::StandardButton button)230 void GTUtilsDialog::clickButtonBox(GUITestOpStatus &os, QWidget *dialog, QDialogButtonBox::StandardButton button) {
231 #ifdef Q_OS_DARWIN
232     QMessageBox *mbox = qobject_cast<QMessageBox *>(dialog);
233     GTUtilsMac fakeClock;
234     fakeClock.startWorkaroundForMacCGEvents(16000, false);
235     if (mbox != NULL && (button == QDialogButtonBox::Yes || button == QDialogButtonBox::No || button == QDialogButtonBox::NoToAll)) {
236         QMessageBox::StandardButton btn =
237             button == QDialogButtonBox::Yes       ? QMessageBox::Yes
238             : button == QDialogButtonBox::NoToAll ? QMessageBox::NoToAll
239                                                   : QMessageBox::No;
240         QAbstractButton *pushButton = mbox->button(btn);
241         GT_CHECK(pushButton != NULL, "pushButton is NULL");
242         GTWidget::click(os, pushButton);
243     } else {
244         QDialogButtonBox *box = buttonBox(os, dialog);
245         GT_CHECK(box != NULL, "buttonBox is NULL");
246         QPushButton *pushButton = box->button(button);
247         GT_CHECK(pushButton != NULL, "pushButton is NULL");
248         GTWidget::click(os, pushButton);
249     }
250 #else
251     QDialogButtonBox *box = buttonBox(os, dialog);
252     GT_CHECK(box != NULL, "buttonBox is NULL");
253     QPushButton *pushButton = box->button(button);
254     GT_CHECK(pushButton != NULL, "pushButton is NULL");
255     GTWidget::click(os, pushButton);
256 #endif
257 }
258 #undef GT_METHOD_NAME
259 
waitForDialog(GUITestOpStatus & os,Runnable * r,const GUIDialogWaiter::WaitSettings & settings)260 void GTUtilsDialog::waitForDialog(GUITestOpStatus &os, Runnable *r, const GUIDialogWaiter::WaitSettings &settings) {
261     pool.prepend(new GUIDialogWaiter(os, r, settings));
262 }
263 
waitForDialog(GUITestOpStatus & os,Runnable * r,int timeout)264 void GTUtilsDialog::waitForDialog(GUITestOpStatus &os, Runnable *r, int timeout) {
265     GUIDialogWaiter::WaitSettings settings;
266     Filler *f = dynamic_cast<Filler *>(r);
267     if (f) {
268         settings = f->getSettings();
269         if (timeout > 0) {
270             settings.timeout = timeout;
271         }
272     }
273     waitForDialog(os, r, settings);
274 }
275 
waitForDialogWhichMustNotBeRun(GUITestOpStatus & os,Runnable * r)276 void GTUtilsDialog::waitForDialogWhichMustNotBeRun(GUITestOpStatus &os, Runnable *r) {
277     GUIDialogWaiter::WaitSettings settings;
278     Filler *f = dynamic_cast<Filler *>(r);
279     if (f) {
280         settings = f->getSettings();
281     }
282 
283     settings.destiny = GUIDialogWaiter::MustNotBeRun;
284     waitForDialog(os, r, settings);
285 }
286 
waitForDialogWhichMayRunOrNot(GUITestOpStatus & os,Runnable * r)287 void GTUtilsDialog::waitForDialogWhichMayRunOrNot(GUITestOpStatus &os, Runnable *r) {
288     GUIDialogWaiter::WaitSettings settings;
289     Filler *f = dynamic_cast<Filler *>(r);
290     if (f) {
291         settings = f->getSettings();
292     }
293 
294     settings.destiny = GUIDialogWaiter::NoMatter;
295     settings.timeout = 480000;
296     waitForDialog(os, r, settings);
297 }
298 
299 #define GT_METHOD_NAME "waitAllFinished"
waitAllFinished(GUITestOpStatus & os,int timeoutMillis)300 void GTUtilsDialog::waitAllFinished(GUITestOpStatus &os, int timeoutMillis) {
301     bool isAllFinished = pool.isEmpty();
302     for (int time = 0; time < timeoutMillis && !isAllFinished; time += GT_OP_CHECK_MILLIS) {
303         GTGlobals::sleep(time > 0 ? GT_OP_CHECK_MILLIS : 0);
304         isAllFinished = true;
305         foreach (GUIDialogWaiter *waiter, pool) {
306             if (!waiter->isFinished && waiter->getSettings().destiny == GUIDialogWaiter::MustBeRun) {
307                 isAllFinished = false;
308                 break;
309             }
310         }
311     }
312     if (!isAllFinished && !os.hasError()) {
313         GUIDialogWaiter *nonFinishedWaiter = nullptr;
314         foreach (GUIDialogWaiter *waiter, pool) {
315             if (!waiter->isFinished && waiter->getSettings().destiny == GUIDialogWaiter::MustBeRun) {
316                 nonFinishedWaiter = waiter;
317                 break;
318             }
319         }
320         os.setError(QString("There are active waiters after: %1ms. First waiter details: %2")
321                         .arg(timeoutMillis)
322                         .arg(nonFinishedWaiter == nullptr ? "nullptr?" : nonFinishedWaiter->getSettings().objectName));
323     }
324 }
325 #undef GT_METHOD_NAME
326 
removeRunnable(Runnable const * const runnable)327 void GTUtilsDialog::removeRunnable(Runnable const *const runnable) {
328     foreach (GUIDialogWaiter *waiter, pool) {
329         if (waiter->getRunnable() == runnable) {
330             pool.removeOne(waiter);
331             delete waiter;
332         }
333     }
334 }
335 
336 #define GT_METHOD_NAME "checkAllFinished"
checkAllFinished(GUITestOpStatus & os)337 void GTUtilsDialog::checkAllFinished(GUITestOpStatus &os) {
338     Q_UNUSED(os);
339 
340     foreach (GUIDialogWaiter *waiter, pool) {
341         GT_CHECK(waiter != nullptr, "GUIDialogWaiter is null");
342         switch (waiter->getSettings().destiny) {
343             case GUIDialogWaiter::MustBeRun:
344                 GT_CHECK(waiter->isFinished, QString("\"%1\" not run but should be").arg((waiter->getSettings().objectName)));
345                 break;
346             case GUIDialogWaiter::MustNotBeRun:
347                 GT_CHECK(!waiter->isFinished, QString("\"%1\" had run but should not").arg((waiter->getSettings().objectName)));
348                 break;
349             case GUIDialogWaiter::NoMatter:
350                 break;
351         }
352     }
353 }
354 #undef GT_METHOD_NAME
355 
cleanup(GUITestOpStatus & os,CleanupSettings s)356 void GTUtilsDialog::cleanup(GUITestOpStatus &os, CleanupSettings s) {
357     foreach (GUIDialogWaiter *waiter, pool) {
358         waiter->stopTimer();
359     }
360 
361     if (s == FailOnUnfinished) {
362         checkAllFinished(os);
363     }
364 
365     stopHangChecking();
366 
367     qDeleteAll(pool);
368     pool.clear();
369 }
370 
371 #undef GT_CLASS_NAME
372 
Filler(GUITestOpStatus & os,const GUIDialogWaiter::WaitSettings & settings,CustomScenario * scenario)373 Filler::Filler(GUITestOpStatus &os, const GUIDialogWaiter::WaitSettings &settings, CustomScenario *scenario)
374     : os(os), settings(settings), scenario(scenario) {
375 }
376 
Filler(GUITestOpStatus & os,const QString & objectName,CustomScenario * scenario)377 Filler::Filler(GUITestOpStatus &os, const QString &objectName, CustomScenario *scenario)
378     : os(os), settings(GUIDialogWaiter::WaitSettings(objectName)), scenario(scenario) {
379 }
380 
~Filler()381 Filler::~Filler() {
382     delete scenario;
383 }
384 
getSettings() const385 GUIDialogWaiter::WaitSettings Filler::getSettings() const {
386     return settings;
387 }
388 
run()389 void Filler::run() {
390     if (scenario == nullptr) {
391         commonScenario();
392     } else {
393         scenario->run(os);
394     }
395     GTThread::waitForMainThread();
396 }
397 
releaseMouseButtons()398 void Filler::releaseMouseButtons() {
399     Qt::MouseButtons buttons = QGuiApplication::mouseButtons();
400 
401     if (buttons | Qt::LeftButton) {
402         GTMouseDriver::release(Qt::LeftButton);
403     }
404 
405     if (buttons | Qt::RightButton) {
406         GTMouseDriver::release(Qt::RightButton);
407     }
408 }
409 
410 }  // namespace HI
411