Commit 9e833bed authored by Bernhard Beschow's avatar Bernhard Beschow
Browse files

instantiate event loops in runner plugins rather than in RunnerTask and...

instantiate event loops in runner plugins rather than in RunnerTask and instantiate runners in background threads

Instantiating the event loops in the runner plugins rather than in RunnerTask resolves weired usage of QEventLoop: The synchronous runners were finished before QEventLoop::exec() was called. Somehow the finished signal of the runners still managed to quit the loop, such that exec() wouldn't block. Until Qt 4.8 we might just have been lucky...

Instantiating runners in the background thread avoids thread affinity issues wrt. to QEventLoop. In particular, QEventLoop instances would block a background thread forever because calling quit() or exit() wouldn't cause exec() to return.

BUG: 288612
REVIEw: 103711
parent 94acc523
......@@ -124,7 +124,7 @@ QList<RunnerPlugin*> MarbleRunnerManagerPrivate::plugins( RunnerPlugin::Capabili
void MarbleRunnerManagerPrivate::cleanupSearchTask( RunnerTask* task )
{
m_searchTasks.removeAll( task );
mDebug() << "removing task " << m_searchTasks.size() << " " << (long)task;
mDebug() << "removing search task" << m_searchTasks.size() << (long)task;
if ( m_searchTasks.isEmpty() ) {
emit q->searchFinished( m_lastSearchTerm );
emit q->placemarkSearchFinished();
......@@ -205,12 +205,7 @@ void MarbleRunnerManager::findPlacemarks( const QString &searchTerm )
QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Search );
foreach( RunnerPlugin* plugin, plugins ) {
MarbleAbstractRunner* runner = plugin->newRunner();
runner->setParent( this );
connect( runner, SIGNAL( searchFinished( QVector<GeoDataPlacemark*> ) ),
this, SLOT( addSearchResult( QVector<GeoDataPlacemark*> ) ) );
runner->setModel( d->m_marbleModel );
SearchTask* task = new SearchTask( runner, searchTerm );
SearchTask* task = new SearchTask( plugin, this, d->m_marbleModel, searchTerm );
connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupSearchTask( RunnerTask* ) ) );
d->m_searchTasks << task;
mDebug() << "search task " << plugin->nameId() << " " << (long)task;
......@@ -265,12 +260,7 @@ void MarbleRunnerManager::reverseGeocoding( const GeoDataCoordinates &coordinate
d->m_reverseGeocodingResults.removeAll( coordinates );
QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::ReverseGeocoding );
foreach( RunnerPlugin* plugin, plugins ) {
MarbleAbstractRunner* runner = plugin->newRunner();
runner->setParent( this );
connect( runner, SIGNAL( reverseGeocodingFinished( GeoDataCoordinates, GeoDataPlacemark ) ),
this, SLOT( addReverseGeocodingResult( GeoDataCoordinates, GeoDataPlacemark ) ) );
runner->setModel( d->m_marbleModel );
ReverseGeocodingTask* task = new ReverseGeocodingTask( runner, coordinates );
ReverseGeocodingTask* task = new ReverseGeocodingTask( plugin, this, d->m_marbleModel, coordinates );
connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupReverseGeocodingTask(RunnerTask*) ) );
mDebug() << "reverse task " << plugin->nameId() << " " << (long)task;
d->m_reverseTasks << task;
......@@ -324,12 +314,7 @@ void MarbleRunnerManager::retrieveRoute( const RouteRequest *request )
continue;
}
MarbleAbstractRunner* runner = plugin->newRunner();
runner->setParent( this );
connect( runner, SIGNAL( routeCalculated( GeoDataDocument* ) ),
this, SLOT( addRoutingResult( GeoDataDocument* ) ) );
runner->setModel( d->m_marbleModel );
RoutingTask* task = new RoutingTask( runner, request );
RoutingTask* task = new RoutingTask( plugin, this, d->m_marbleModel, request );
connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupRoutingTask( RunnerTask* ) ) );
mDebug() << "route task " << plugin->nameId() << " " << (long)task;
d->m_routingTasks << task;
......@@ -370,10 +355,7 @@ void MarbleRunnerManager::parseFile( const QString &fileName, DocumentRole role
{
QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Parsing );
foreach( RunnerPlugin *plugin, plugins ) {
MarbleAbstractRunner* runner = plugin->newRunner();
connect( runner, SIGNAL( parsingFinished( GeoDataDocument*, QString ) ),
this, SLOT( addParsingResult( GeoDataDocument*, QString )) );
ParsingTask *task = new ParsingTask( runner, fileName, role );
ParsingTask *task = new ParsingTask( plugin, this, fileName, role );
connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupParsingTask(RunnerTask*) ) );
mDebug() << "parse task " << plugin->nameId() << " " << (long)task;
d->m_parsingTasks << task;
......
......@@ -12,6 +12,8 @@
#include "MarbleAbstractRunner.h"
#include "MarbleDebug.h"
#include "MarbleRunnerManager.h"
#include "RunnerPlugin.h"
#include "routing/RouteRequest.h"
#include <QtCore/QTimer>
......@@ -19,88 +21,99 @@
namespace Marble
{
RunnerTask::RunnerTask( MarbleAbstractRunner* runner ) :
m_runner( runner )
RunnerTask::RunnerTask( RunnerPlugin* factory, MarbleRunnerManager *manager ) :
m_factory( factory ),
m_manager( manager )
{
// nothing to do
}
void RunnerTask::run()
{
QTimer watchdog;
watchdog.setSingleShot( true );
QEventLoop localEventLoop;
QObject::connect( &watchdog, SIGNAL( timeout() ), &localEventLoop, SLOT( quit() ) );
runTask( &localEventLoop );
watchdog.start( 30 * 1000 );
QObject::connect( QCoreApplication::instance(), SIGNAL( aboutToQuit() ), &localEventLoop, SLOT( quit() ) );
localEventLoop.exec();
if( watchdog.isActive() ) {
watchdog.stop(); // completed within timeout
} else {
mDebug() << "Timeout reached while waiting for result. Killing the runner.";
}
runner()->deleteLater();
runTask();
emit finished( this );
}
MarbleAbstractRunner* RunnerTask::runner()
RunnerPlugin *RunnerTask::factory()
{
return m_factory;
}
MarbleRunnerManager *RunnerTask::manager()
{
return m_runner;
return m_manager;
}
SearchTask::SearchTask(MarbleAbstractRunner* runner, const QString &searchTerm) :
RunnerTask( runner ), m_searchTerm( searchTerm )
SearchTask::SearchTask(RunnerPlugin* factory, MarbleRunnerManager *manager, MarbleModel *model, const QString &searchTerm) :
RunnerTask( factory, manager ),
m_model( model ),
m_searchTerm( searchTerm )
{
// nothing to do
}
void SearchTask::runTask( QEventLoop *localEventLoop )
void SearchTask::runTask()
{
QObject::connect( runner(), SIGNAL( searchFinished( QVector<GeoDataPlacemark*> ) ),
localEventLoop, SLOT( quit() ) );
runner()->search( m_searchTerm );
MarbleAbstractRunner *runner = factory()->newRunner();
connect( runner, SIGNAL( searchFinished( QVector<GeoDataPlacemark*> ) ),
manager(), SLOT( addSearchResult( QVector<GeoDataPlacemark*> ) ) );
runner->setModel( m_model );
runner->search( m_searchTerm );
runner->deleteLater();
}
ReverseGeocodingTask::ReverseGeocodingTask( MarbleAbstractRunner* runner, const GeoDataCoordinates &coordinates ) :
RunnerTask( runner ), m_coordinates( coordinates )
ReverseGeocodingTask::ReverseGeocodingTask( RunnerPlugin* factory, MarbleRunnerManager *manager, MarbleModel *model, const GeoDataCoordinates &coordinates ) :
RunnerTask( factory, manager ),
m_model( model ),
m_coordinates( coordinates )
{
// nothing to do
}
void ReverseGeocodingTask::runTask( QEventLoop *localEventLoop )
void ReverseGeocodingTask::runTask()
{
QObject::connect( runner(), SIGNAL( reverseGeocodingFinished( GeoDataCoordinates, GeoDataPlacemark) ),
localEventLoop, SLOT( quit() ) );
runner()->reverseGeocoding( m_coordinates );
MarbleAbstractRunner *runner = factory()->newRunner();
connect( runner, SIGNAL( reverseGeocodingFinished( GeoDataCoordinates, GeoDataPlacemark ) ),
manager(), SLOT( addReverseGeocodingResult( GeoDataCoordinates, GeoDataPlacemark ) ) );
runner->setModel( m_model );
runner->reverseGeocoding( m_coordinates );
runner->deleteLater();
}
RoutingTask::RoutingTask( MarbleAbstractRunner* runner, const RouteRequest* routeRequest ) :
RunnerTask( runner ), m_routeRequest( routeRequest )
RoutingTask::RoutingTask( RunnerPlugin* factory, MarbleRunnerManager *manager, MarbleModel *model, const RouteRequest* routeRequest ) :
RunnerTask( factory, manager ),
m_model( model ),
m_routeRequest( routeRequest )
{
// nothing to do
}
void RoutingTask::runTask( QEventLoop *localEventLoop )
void RoutingTask::runTask()
{
QObject::connect( runner(), SIGNAL( routeCalculated( GeoDataDocument*) ),
localEventLoop, SLOT( quit() ) );
runner()->retrieveRoute( m_routeRequest );
MarbleAbstractRunner *runner = factory()->newRunner();
connect( runner, SIGNAL( routeCalculated( GeoDataDocument* ) ),
manager(), SLOT( addRoutingResult( GeoDataDocument* ) ) );
runner->setModel( m_model );
runner->retrieveRoute( m_routeRequest );
runner->deleteLater();
}
ParsingTask::ParsingTask( MarbleAbstractRunner* runner, const QString& fileName, DocumentRole role ) :
RunnerTask( runner ), m_fileName( fileName ), m_role( role )
ParsingTask::ParsingTask( RunnerPlugin *factory, MarbleRunnerManager *manager, const QString& fileName, DocumentRole role ) :
RunnerTask( factory, manager ),
m_fileName( fileName ),
m_role( role )
{
// nothing to do
}
void ParsingTask::runTask( QEventLoop *localEventLoop )
void ParsingTask::runTask()
{
QObject::connect( runner(), SIGNAL( parsingFinished(GeoDataDocument*) ),
localEventLoop, SLOT( quit() ) );
runner()->parseFile( m_fileName, m_role );
MarbleAbstractRunner *runner = factory()->newRunner();
connect( runner, SIGNAL( parsingFinished( GeoDataDocument*, QString ) ),
manager(), SLOT( addParsingResult( GeoDataDocument*, QString ) ) );
runner->parseFile( m_fileName, m_role );
runner->deleteLater();
}
}
......
......@@ -20,8 +20,11 @@
namespace Marble
{
class MarbleModel;
class RouteRequest;
class RunnerPlugin;
class MarbleAbstractRunner;
class MarbleRunnerManager;
/**
* An abstract QRunnable that executes one of the MarbleAbstractRunner tasks -- placemark
......@@ -34,7 +37,7 @@ class RunnerTask : public QObject, public QRunnable
public:
/** Constructor. The runner instance given will be used to execute the actual task */
explicit RunnerTask( MarbleAbstractRunner* runner );
explicit RunnerTask( Marble::RunnerPlugin *factory, MarbleRunnerManager *manager );
/** Overriding QRunnable to execute the runner task in a local event loop */
virtual void run();
......@@ -44,63 +47,77 @@ Q_SIGNALS:
protected:
/** Derived classes should execute their task and quit the provided event loop when done */
virtual void runTask( QEventLoop *localEventLoop ) = 0;
virtual void runTask() = 0;
/** Access to the runner for derived classes */
MarbleAbstractRunner* runner();
RunnerPlugin* factory();
MarbleRunnerManager *manager();
private:
MarbleAbstractRunner* m_runner;
RunnerPlugin *const m_factory;
MarbleRunnerManager *const m_manager;
};
/** A RunnerTask that executes a placemark search */
class SearchTask : public RunnerTask
{
Q_OBJECT
public:
SearchTask( MarbleAbstractRunner* runner, const QString &searchTerm );
SearchTask( RunnerPlugin *factory, MarbleRunnerManager *manager, MarbleModel *model, const QString &searchTerm );
virtual void runTask( QEventLoop *localEventLoop );
virtual void runTask();
private:
QString m_searchTerm;
MarbleModel *const m_model;
QString m_searchTerm;
};
/** A RunnerTask that executes reverse geocoding */
class ReverseGeocodingTask : public RunnerTask
{
Q_OBJECT
public:
ReverseGeocodingTask( MarbleAbstractRunner* runner, const GeoDataCoordinates &coordinates );
ReverseGeocodingTask( RunnerPlugin *factory, MarbleRunnerManager *manager, MarbleModel *model, const GeoDataCoordinates &coordinates );
virtual void runTask( QEventLoop *localEventLoop );
virtual void runTask();
private:
GeoDataCoordinates m_coordinates;
MarbleModel *const m_model;
GeoDataCoordinates m_coordinates;
};
/** A RunnerTask that executes a route calculation */
class RoutingTask : public RunnerTask
{
Q_OBJECT
public:
RoutingTask( MarbleAbstractRunner* runner, const RouteRequest* routeRequest );
RoutingTask( RunnerPlugin *factory, MarbleRunnerManager *manager, MarbleModel *model, const RouteRequest* routeRequest );
virtual void runTask( QEventLoop *localEventLoop );
virtual void runTask();
private:
MarbleModel *const m_model;
const RouteRequest *const m_routeRequest;
};
/** A RunnerTask that executes a file Parsing */
class ParsingTask : public RunnerTask
{
Q_OBJECT
public:
ParsingTask( MarbleAbstractRunner* runner, const QString& fileName, DocumentRole role );
ParsingTask( RunnerPlugin* factory, MarbleRunnerManager *manager, const QString& fileName, DocumentRole role );
virtual void runTask( QEventLoop *localEventLoop );
virtual void runTask();
private:
QString m_fileName;
DocumentRole m_role;
QString m_fileName;
DocumentRole m_role;
};
}
......
......@@ -54,8 +54,15 @@ void HostipRunner::search( const QString &searchTerm )
slotNoResults();
}
else {
QEventLoop eventLoop;
connect( this, SIGNAL( searchFinished( QVector<GeoDataPlacemark*> ) ),
&eventLoop, SLOT( quit() ) );
// Lookup the IP address for a hostname, or the hostname if an IP address was given
QHostInfo ::lookupHost( searchTerm, this, SLOT(slotLookupFinished(QHostInfo)));
eventLoop.exec();
}
}
......
......@@ -65,8 +65,15 @@ void OsmNominatimRunner::search( const QString &searchTerm )
m_request.setUrl(QUrl(url));
m_request.setRawHeader("User-Agent", TinyWebBrowser::userAgent("Browser", "OsmNominatimRunner") );
QEventLoop eventLoop;
connect( this, SIGNAL( searchFinished( QVector<GeoDataPlacemark*> ) ),
&eventLoop, SLOT( quit() ) );
// @todo FIXME Must currently be done in the main thread, see bug 257376
QTimer::singleShot( 0, this, SLOT( startSearch() ) );
eventLoop.exec();
}
void OsmNominatimRunner::reverseGeocoding( const GeoDataCoordinates &coordinates )
......@@ -82,8 +89,15 @@ void OsmNominatimRunner::reverseGeocoding( const GeoDataCoordinates &coordinates
m_request.setUrl(QUrl(url));
m_request.setRawHeader("User-Agent", TinyWebBrowser::userAgent("Browser", "OsmNominatimRunner") );
QEventLoop eventLoop;
connect( this, SIGNAL( reverseGeocodingFinished( GeoDataCoordinates, GeoDataPlacemark ) ),
&eventLoop, SLOT( quit() ) );
// @todo FIXME Must currently be done in the main thread, see bug 257376
QTimer::singleShot( 0, this, SLOT( startReverseGeocoding() ) );
eventLoop.exec();
}
void OsmNominatimRunner::startSearch()
......
......@@ -89,8 +89,15 @@ void OpenRouteServiceRunner::retrieveRoute( const RouteRequest *route )
m_request.setHeader( QNetworkRequest::ContentTypeHeader, "application/xml" );
m_requestData = request.toLatin1();
QEventLoop eventLoop;
connect( this, SIGNAL( routeCalculated( GeoDataDocument* ) ),
&eventLoop, SLOT( quit() ) );
// @todo FIXME Must currently be done in the main thread, see bug 257376
QTimer::singleShot( 0, this, SLOT( get() ) );
eventLoop.exec();
}
void OpenRouteServiceRunner::get()
......
......@@ -74,8 +74,16 @@ void YoursRunner::retrieveRoute( const RouteRequest *route )
// mDebug() << "GET: " << request;
m_request = QNetworkRequest( QUrl( request ) );
QEventLoop eventLoop;
connect( this, SIGNAL( routeCalculated( GeoDataDocument* ) ),
&eventLoop, SLOT( quit() ) );
// @todo FIXME Must currently be done in the main thread, see bug 257376
QTimer::singleShot( 0, this, SLOT( get() ) );
eventLoop.exec();
}
void YoursRunner::get()
......
......@@ -10,13 +10,19 @@
#include <QtTest/QtTest>
#include <QtTest/QSignalSpy>
#include <QtCore/QMetaType>
#include "MarbleDebug.h"
#include "MarbleDirs.h"
#include "MarbleRunnerManager.h"
#include "PluginManager.h"
#include "GeoDataPlacemark.h"
#include "routing/RouteRequest.h"
#define addRow() QTest::newRow( QString("line %1").arg( __LINE__ ).toAscii().data() )
Q_DECLARE_METATYPE( QList<Marble::GeoDataCoordinates> )
namespace Marble
{
......@@ -31,15 +37,23 @@ private slots:
void cleanup(){};// will be called after every testfunction.
void testSyncPlacemarks();
void testAsyncPlacemarks_data();
void testAsyncPlacemarks();
void testSyncReverse();
void testAsyncReverse_data();
void testAsyncReverse();
void testSyncRouting();
void testAsyncRouting_data();
void testAsyncRouting();
void testSyncParsing();
void testAsyncParsing_data();
void testAsyncParsing();
public:
......@@ -54,11 +68,14 @@ public:
void MarbleRunnerManagerTest::initTestCase()
{
// MarbleDebug::enable = true;
MarbleDirs::setMarbleDataPath( DATA_PATH );
MarbleDirs::setMarblePluginPath( PLUGIN_PATH );
m_time = 30000;
m_name = "Berlin";
qRegisterMetaType<QList<GeoDataCoordinates> >( "QList<GeoDataCoordinates>" );
m_coords.setLatitude(52.50160, GeoDataCoordinates::Degree );
m_coords.setLongitude(13.40233, GeoDataCoordinates::Degree );
......@@ -107,20 +124,29 @@ void MarbleRunnerManagerTest::testSyncPlacemarks()
QCOMPARE( finishSpy.count(), 1 );
}
void MarbleRunnerManagerTest::testAsyncPlacemarks_data()
{
QTest::addColumn<QString>( "name" );
addRow() << QString( "Berlin" );
addRow() << QString( "www.heise.de" );
}
void MarbleRunnerManagerTest::testAsyncPlacemarks()
{
MarbleRunnerManager m_runnerManager(&m_pluginManager, this);
QSignalSpy finishSpy( &m_runnerManager, SIGNAL(placemarkSearchFinished()) );
QSignalSpy resultSpy( &m_runnerManager, SIGNAL(searchResultChanged(QVector<GeoDataPlacemark*>)) );
QSignalSpy finishSpy( &m_runnerManager, SIGNAL( searchFinished( QString ) ) );
QSignalSpy resultSpy( &m_runnerManager, SIGNAL( searchResultChanged( QVector<GeoDataPlacemark*> ) ) );
QCOMPARE( finishSpy.count(), 0 );
QCOMPARE( resultSpy.count(), 0 );
QEventLoop loop;
connect( &m_runnerManager, SIGNAL( searchFinished( QString ) ),
&loop, SLOT( quit() ) );
QTime timer;
timer.start();
m_runnerManager.findPlacemarks(m_name);
QTest::qWait(m_time);
QFETCH( QString, name );
m_runnerManager.findPlacemarks( name );
loop.exec();
QCOMPARE( resultSpy.count(), 1 );
QCOMPARE( finishSpy.count(), 1 );
......@@ -159,6 +185,13 @@ void MarbleRunnerManagerTest::testSyncReverse()
QCOMPARE( finishSpy.count(), 1 );
}
void MarbleRunnerManagerTest::testAsyncReverse_data()
{
QTest::addColumn<GeoDataCoordinates>( "coordinates" );
addRow() << m_coords;
}
void MarbleRunnerManagerTest::testAsyncReverse()
{
MarbleRunnerManager m_runnerManager(&m_pluginManager, this);
......@@ -166,11 +199,14 @@ void MarbleRunnerManagerTest::testAsyncReverse()
QSignalSpy finishSpy( &m_runnerManager, SIGNAL(reverseGeocodingFinished()) );
QSignalSpy resultSpy( &m_runnerManager, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)) );
QCOMPARE( finishSpy.count(), 0 );
QCOMPARE( resultSpy.count(), 0 );
QEventLoop loop;
connect( &m_runnerManager, SIGNAL( reverseGeocodingFinished() ),
&loop, SLOT( quit() ) );
m_runnerManager.reverseGeocoding( m_coords );
QTest::qWait( m_time );
QFETCH( GeoDataCoordinates, coordinates );
m_runnerManager.reverseGeocoding( coordinates );
loop.exec();
QCOMPARE( resultSpy.count(), 1 );
QCOMPARE( finishSpy.count(), 1 );
......@@ -198,6 +234,13 @@ void MarbleRunnerManagerTest::testSyncRouting()
QCOMPARE( finishSpy.count(), 1 );
}
void MarbleRunnerManagerTest::testAsyncRouting_data()
{
QTest::addColumn<QList<GeoDataCoordinates> >( "coordinatesList" );
addRow() << ( QList<GeoDataCoordinates>() << m_coords << m_coords2 );
}
void MarbleRunnerManagerTest::testAsyncRouting()
{
MarbleRunnerManager m_runnerManager(&m_pluginManager, this);
......@@ -205,11 +248,19 @@ void MarbleRunnerManagerTest::testAsyncRouting()
QSignalSpy finishSpy( &m_runnerManager, SIGNAL(routingFinished()) );
QSignalSpy resultSpy( &m_runnerManager, SIGNAL( routeRetrieved(GeoDataDocument*)) );
QCOMPARE( resultSpy.count(), 0 );
QCOMPARE( finishSpy.count(), 0 );
QEventLoop loop;
connect( &m_runnerManager, SIGNAL( routingFinished() ),
&loop, SLOT( quit() ) );
QFETCH( QList<GeoDataCoordinates>, coordinatesList );
RouteRequest request;
foreach( const GeoDataCoordinates &coordinates, coordinatesList ) {
request.append( coordinates );
}
m_runnerManager.retrieveRoute( &m_request );
QTest::qWait( m_time );
m_runnerManager.retrieveRoute( &request );
loop.exec();
QVERIFY( resultSpy.count() > 0 );
QCOMPARE( finishSpy.count(), 1 );
......@@ -237,6 +288,13 @@ void MarbleRunnerManagerTest::testSyncParsing()
QCOMPARE( finishSpy.count(), 1 );
}
void MarbleRunnerManagerTest::testAsyncParsing_data()
{
QTest::addColumn<QString>( "fileName" );
addRow() << MarbleDirs::path( "placemarks/otherplacemarks.cache" );
}
void MarbleRunnerManagerTest::testAsyncParsing()
{
MarbleRunnerManager m_runnerManager(&m_pluginManager, this);
......@@ -244,11 +302,16 @@ void MarbleRunnerManagerTest::testAsyncParsing()
QSignalSpy finishSpy( &m_runnerManager, SIGNAL( parsingFinished() ) );
QSignalSpy resultSpy( &m_runnerManager, SIGNAL( parsingFinished(GeoDataDocument*,QString)) );
QCOMPARE( resultSpy.count(), 0 );
QCOMPARE( finishSpy.count(), 0 );
QEventLoop loop;
connect( &m_runnerManager, SIGNAL( parsingFinished() ),
&loop, SLOT( quit() ) );
QFETCH( QString, fileName );
m_runnerManager.parseFile( fileName );
loop.exec();
m_runnerManager.parseFile( m_fileName );