I am investigating QTest for GUI testing. It appears that there is no mechanism in QTest to asynchronously test a signal callback. Am I misunderstanding how to use QTest, or misunderstanding the intended functionality provided by QTest?
For example, I am attempting to test a signal which launches a modal QMessageBox popup in response to clicking a QPushButton. I want to test events and state between the clicking of the button and clicking 'OK' on the QMessageBox. I have tried using QSignalSpy.wait(), QTestEventList.simulate(), QTest::mouseClick(), QTest::mouseEvent(), and QTRY_VERIFY(), all of which, when called from the testing code, do not return until after clicking 'OK' on the QMessageBox popup. Therefore, to test anything before all event handlers return, I would have to do something like install an event filter on the object under test, and write my own asynchronous testing code.
This is all the same code we would have to write if we weren't using a test library and we can do all of this without using QTest. For example, we can manually get a handle to the object under test, connect signals for testing, invoke an event to trigger the event handler or invoke the signal manually, and also write the installed test handlers that interact with the test environment before returning execution to the point at which the event was launched. What does QTest gain us here?
(Also posted on Qt forums)
Working with synchronous events using qtestlib is a bit tricky. If you look into the sources of qtestlib, you can find that event simulation are pretty straightforward. So, qtestlib doesn't provide any methods to handle synchronous events. Anyway, it's possible to handle Qt modal windows which are spawned by your app.
Main note to this question is that GUI objects can't be accessed from others threads except GUI thread. And moreover GUI could be created only in thread where QApplication was created. So some tricks like spawning a new thread to press OK button in QMessageBox will be unsuccessful with error like this object can not be accessed from other thread somewhere in QWidget sources.
To avoid this case async event could be triggered with Qt slots mechanism. First of all you should define a Helper object with some slot, for example
class Helper {
Helper() {}
public slots:
doSmth();
}
Further you should create an instance of this object in the testcase
void BlahblahTest::testCase1() {
Helper h;
...
And before you invoke some synchronous event with, for example, QTest::mouseClick, set a delayed action with
QTimer::singleShot(delay, &h, SLOT(doSmth));
Depends on your needs the implementation of doSmth could be like that
void Helper::doSmth() {
QList<QWidget *> tlw = qApp()->topLevelWidgets();
foreach (QWidget *w, tlw) {
if (...) { // w is a messagebox
Qt::keyClick(w, Qt::Key_Enter);
}
}
}
Related
I've got a WinForm project which uses a PageControl and PageTabs. Say there are two PageTabs, each with their own UserControl object. If one UserControl starts a Thread() which is meant to loop endlessly and access a TextBox on the UserControl that started it, how does that thread's process access the correct UserControl.
More specifically:
The GUI.h has two PageTabs, each with their own UserControl object.
The first tab has a ReceiveButton which starts a thread. That thread does a lot of work on a loop and updates the TextBox.
The second tab is essentially the same, but has it's own ReceiveButton and TextBox. This button also starts a thread and is supposed to update this TextBox.
I'm having a tough time figuring out how to make each thread access/update it's own respective TextBox.
Here's sort of the chain of code my UserControl follows:
//MyUserControl.h
void ContinueNormally(void);
System::Void buttonReceive_Click(System::Object^ sender, System::EventArgs^ e)
{
this->myThread = gcnew System::Threading::Thread(gcnew System::Threading::ThreadStart(this, &MyUserControl::ContinueNormally));
this->myThread->Start();
}
//GUI.h
#include "MyUserControl.h"
void BufferRecieveLoop()
{
while (true)
{
//receive from multicast
incoming.Process(buffer, bytes_read, endian); //this is the method in the other .h file
}
}
void MyUserControl::ContinueNormally()
{
//setup
BufferRecieveLoop();
//cleanup
}
//EntityStateProcessorPdu.h
#include "MyUserControl.h"
void EntityStatePduProcessor::Process(const DIS::Pdu& packet)
{
//do stuff
///Below are attempts at accessing the correct textbox, all in vain :(
//GUI_Example_Receive::Globals::gui->SetConsoleTextBoxText(sysStr);
//GUI_Example_Receive::Globals::gui->Controls->Find("myUserControl", true)[0]->Controls->Find("")
//GUI_Example_Receive::MyUserControl::SetTextBoxConsoleText(sysStr);
}
I should also note that this works just fine with one UserControl, or one PageTab. I'm Invoking correctly to get the UI thread to do the updating.
In Win32 (and pretty much any OS GUI that I know of) only allows one "thread" to access the GUI. This is normally known as the "GUI Thread".
The "GUI Thread" is the thread that runs the Win32 message pump.
Normally what you do is post your own "custom" messages that will be run in the GUI thread and you update your GUI controls from there.
I don't know the C++ GUI library you are using but it will most likely provide some sort of utility for post messages or code to be run on the GUI thread.
In Windows Forms every controls has a method Invoke, that it does what you search:
calls a delegate in the same thread that creates the control.
At the moment I am developing a Windows DLL with Qt 5.9.2 (MSVC 2015 compiler), which should be loaded by an existing, commercial MFC application. Upon request of this application a modal instance of QDialog should be displayed.
Since QApplication::exec() would block the entire application, I "simulate" the event loop using the following code:
void Core::createQApplicationInstance()
{
// Check, if there's already a 'QApplication' instance running (unlikely)
if (!QApplication::instance())
{
int argc = 1;
// Create a new 'QApplication' instance
m_app = new QApplication(argc, nullptr);
// Create a 'QTimer' instance to call 'processEvents' periodically:
// We can't run 'm_app->exec()' because it would block everything,
// so we'll use this 'hacky-whacky' method here
m_timer = new QTimer;
// Connect the timer's timeout to the app's 'processEvents' via a lambda
QObject::connect(
m_timer,
&QTimer::timeout,
[&]()
{
m_app->processEvents();
}
);
// Start the timer with the fixed 'message' interval
m_timer->start(kMsgInterval);
}
}
If my DLL should now display a modal dialog, it works (partially) with the following code:
{...}
case eUserIGeneral:
{
qDebug() << "<< eUserIGeneral";
QDialog w;
w.setModal(true);
w.exec();
// --> Code here is executed AFTER the dialog has been closed
}
break;
//-------------------------------------------------------------------
{...}
The code after w.exec() will actually be executed AFTER the dialog was closed (as intended). However, the main application still remains responsive and is not affected by the modality of my dialog, which Is not as I expected it to behave.
How can I make sure that inputs in the main application are locked when calling a modal DLL dialog?
Although I don't have a real answer to your question, there too much stuff wrong with your code to be properly explained in a comment. Therefore I am writing this as an answer.
QApplication::exec(): I strongly recommend revising the decision against it. If you want the window to be modal, why would it be wrong to "block the entire application" until it is closed? Note that you will not block the Qt part of the application, only the one that calls exec.
QTimer: A timer can only run inside an event loop. So the m_app->processEvents() either never executes, or you already have an event loop running. Either way, there is no use for the timer.
w.setModal(): If what this does is not correct for you, check out setWindowModality().
w.exec(): Ignores the value of setModal(). Read the documentation of setModal() and exec() to find out more.
w.exec(): Executes an event loop. If this somewhat does what you want, QApplication::exec() should work too. Just make sure to exit the main event loop when done.
w.exec(): Is not executed after the dialog was closed. It is executes while the dialog is shown. It blocks until the dialog is closed. So you will start executing it, show the dialog, close the dialog, and then return from it. Read the documentation of exec() to find out more.
I have a large MFC based application that includes some potentially very slow tasks in the main thread. This can give the appearance that the application has hung when it is actually working its way through a long task. From a usability point of view, I'd like to be giving the user some more feedback on progress, and have an option to abort the task in a clean manner. While hiving the long tasks off into separate threads would be a better long term solution, I'm thinking a pragmatic short term solution is create a new GUI thread encapsulated in its own object complete with dialog including progress bar and cancel button, used in a similar manner to a CWait object. The main thread monitors the cancel status via an IsCancelled method, and finishes via a throw when required.
Is this a reasonable approach, and if so is there some MFC code out there already that I can use, or should I roll my own? First sketch looks like this
class CProgressThread : public CWinThread
{
public:
CProgressThread(int ProgressMax);
~CProgressThread()
void SetProgress(int Progress);
BOOL IsCancelled();
private:
CProgressDialog *theDialog;
}
void MySlowTask()
{
CProgressThread PT(MaxProgress);
try
{
{
{ // deep in the depths of my slow task
PT.SetProgress(Progress);
if (PT.IsCancelled())
throw new CUserHasHadEnough;
}
}
}
catch (CUserHasHadEnough *pUserHasHadEnough)
{
// Clean-up
}
}
As a rule, I tend to have one GUI thread and many worker threads, but this approach could possibly save me a bunch of refactoring and testing. Any serious potential pitfalls?
Short answer, Yes, you can have multiple GUI thread in MFC. But you can't access the GUI component directly other than the created thread. The reason is because the Win32 under the MFC stores the GUI handler per thread based. It means the handler in one thread isn't visible to another thread. If you jump to the CWinThread class source code, you can find a handler map attribute there.
Windows (MFC) doesn't has hard difference between the worker thread & GUI thread. Any thread can be changed to GUI thread once they create the message queue, which is created after the first call related to the message, such as GetMessage().
In your above code, if the progress bar is created in one thread and MySlowWork() is called in another thread. You can only use the CProgressThread attributes without touch the Win32 GUI related functions, such as close, setText, SetProgress... since they all need the GUI handler. If you do call those function, the error will be can't find the specified window since that handler isn't in the thread handler mapping.
If you do need change the GUI, you need send the message to that progress bar owner thread. Let that thread handles the message by itself (message handler) through the PostThreadMessage, refer to MSDN for detail.
I'm attempting to implement a simple, lightweight system for recording Qt GUI events and playing them back from a script. I thought this would be fairly straightforward using the magic of Qt's event system, but I'm running into a problem I don't understand.
Here's quick summary of what I'm doing:
RECORDING:
I use QApplication.instance().eventFilter() to capture all GUI events I'm interested in* and save them to a Python script, in which each step looks something like this:
obj = get_named_object('MainWindow.my_menubar')
recorded_event = QMouseEvent(2, PyQt4.QtCore.QPoint(45, 8), 1, Qt.MouseButtons(0x1), Qt.KeyboardModifiers(0x0))
post_event(obj, recorded_event)
PLAYBACK:
I simply execute the script above, in a worker (non-GUI) thread. (I can't use the GUI thread because I want to keep sending scripted events to the application, even if the 'main' eventloop is blocked while a modal dialog eventloop is running.)
The important stuff happens in my post_event() function, which needs to do two things:
First, call QApplication.postEvent(obj, recorded_event)
Wait for all events to finish processing:**
Post a special event to the same eventloop that obj is running in.
When the special event is handled:
Call QApplication.processEvents()
Set a flag that tells the playback thread it's okay to continue
After the second part is complete, my expectation is that all effects of the first part (the recorded event) have completed, since the special event was queued after the recorded event.
The whole system mostly seems to work just fine for mouse events, key events, etc. But I'm having a problem with QAction handlers when I attempt to playback events for my main QMenuBar.
No matter what I try, it seems that I can't force my playback thread to block for the completion of all QAction.triggered handlers that result from clicking on my QMenu items. As far as I can tell, QApplication.processEvents() is returning before the QAction handler is complete.
Is there something special about QMenu widgets or QAction signals that breaks the normal rules for QApplication.postEvent() and/or QApplication.processEvents()? I need a way to block for the completion of my QMenu's QAction handlers.
[*] Not every event is recorded. I only record spontaneous() events, and I also filter out a few other types (e.g. Paint events and ordinary mouse movements).
[**] This is important because the next event in the script might refer to a widget that was created by the previous event.
I think your problem might best be served by using QFuture and QFutureWatcher (that is, if you're using the QtConcurrent namespace for threads, and not QThreads). Basically, the Qt Event handling system does NOT necessarily handle events in the order they're posted. If you need to block until a certain action is completed, and you're doing that action in a separate thread, you can use the QFuture object returned by QtConcurrent::run() with a QFutureWatcher to block until that particular thread finishes its processing.
Something else to consider is the way you handle events. When you use QApplication.postEvent(), the event you create gets added to the receiver's event queue to be handled later. Behind the scenes, Qt can reorder and compress these events to save processor time. I suspect this is more your problem.
In your function which handles playback, consider using QCoreApplication::processEvents(), which will not return until all events have finished processing. Documentation for QCoreApplication is here.
QMenu widgets and QAction signals are a special case. QMenu has an exec() function, normally used for popups. I suspect (but I don't know for sure) that QMenuBar would use this mechanism when it opens a regular pull-down menu. The docs are not clear about this, but Menus act a lot like dialog boxes in that they block all other user activity - how would Qt do this except by giving menus their own event loop? I can't fill in all the blanks from the information in your post, but I don't see how your playback thread would cope with a new event loop.
I am trying to write a unit test for a GUI application using the QTestLib. The problem is that one of the slots creates a modal dialog using exec() and I found no possibility to interact with the dialog.
The slots which creates the dialog is connected to a QAction. So the first problem is that the test blocks when I trigger the QAction in the test since this results in the call to exec(). Therefore, I tried creating a QThread that performs the interaction. However, this did not help.
Things I already tried (all performed from within the "interaction helper" thread):
Send key clicks using QTest::keyClicks()
Results in error message "QCoreApplication::sendEvent(): Cannot send events to objects owned by a different thread"
Post QKeyEvents using QCoreApplication::postEvent()
Doesn't work, i.e. nothing happens. I guess because the events end up in the event loop of the thread that owns the dialog, which will not be reached until the dialog is closed and exec() returns. See Edit below
Invoking Slots on the dialog using QMetaObject::invokeMethod()
Doesn't work, i.e. nothing happens. I guess for the same reason as postEvent() doesn't work. See Edit below
So the question is: Is there any way to interact programmatically with a modal dialog that was opened using the exec() method?
Edit: Actually, method 3 is working. The problem was a different one:
I passed the arguments to invokeMethod() to the "interaction helper" thread and for some reason, accessing the arguments did not work from that thread (I got no SEG errors but they were simply empty).
I guess that method 2 is also working and I simply had the same problem as with method 3 but I didn't test that.
The solution I use in command line applications which use Qt libraries meant for GUIs is the singleShot, as this answer alludes. In those cases it looks like this:
QCoreApplication app(argc, argv);
// ...
QTimer::singleShot(0, &app, SLOT(quit()));
return app.exec();
So in your case I imagine it would look something like this:
QDialog * p_modalDialog = getThePointer(); // you will have to replace this with
// a real way of getting the pointer
QTimer::singleShot(0, p_modalDialog, SLOT(accept()));
p_modalDialog->exec(); // called somewhere else in your case
// but it will be automatically accepted.
You can keep the interaction in the same thread by delaying its execution until the dialog event loop starts.
For example just before the exec() call, you use either QTimer::singleShot with 0 as interval or QMetaObject::invokeMethod with connection type Qt::QueuedConnection to invoke the slot that needs to be executed while the dialog is shown.
You can also post the events before calling exec().
As soon as the dialog has been constructed after the exec() call, the events will be executed.
For example to test an Esc key press (means reject/close the dialog):
// create a dialog
QDialog d = ...
//post an Escape key press and release event
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyPress , Qt::Key_Escape, Qt::NoModifier) );
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier) );
// execute and check result
int ret = d.exec();
QCOMPARE(ret, static_cast<int>(QDialog::Rejected));
related question's answer has some extra details about flushing the event queue during a test:
Qt event loop and unit testing?