Skip to content

Commit

Permalink
Add method to retrieve exit status of process
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jan 8, 2021
1 parent 1444ce3 commit ddf19ac
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 8 deletions.
5 changes: 5 additions & 0 deletions python/core/auto_generated/qgsrunprocess.sip.in
Expand Up @@ -119,6 +119,11 @@ Runs the process, and blocks until execution finishes.
The optional ``feedback`` argument can be used to specify a feedback object for cancellation/process termination. The optional ``feedback`` argument can be used to specify a feedback object for cancellation/process termination.


After execution completes, the process' result code will be returned. After execution completes, the process' result code will be returned.
%End

QProcess::ExitStatus exitStatus() const;
%Docstring
After a call to :py:func:`~QgsBlockingProcess.run`, returns the process' exit status.
%End %End


}; };
Expand Down
14 changes: 10 additions & 4 deletions src/core/qgsrunprocess.cpp
Expand Up @@ -270,9 +270,9 @@ int QgsBlockingProcess::run( QgsFeedback *feedback )
const bool requestMadeFromMainThread = QThread::currentThread() == QCoreApplication::instance()->thread(); const bool requestMadeFromMainThread = QThread::currentThread() == QCoreApplication::instance()->thread();


int result = 0; int result = 0;
QProcess::ExitStatus status = QProcess::NormalExit; QProcess::ExitStatus exitStatus = QProcess::NormalExit;


std::function<void()> runFunction = [ this, &result, &status, feedback]() std::function<void()> runFunction = [ this, &result, &exitStatus, feedback]()
{ {
// this function will always be run in worker threads -- either the blocking call is being made in a worker thread, // this function will always be run in worker threads -- either the blocking call is being made in a worker thread,
// or the blocking call has been made from the main thread and we've fired up a new thread for this function // or the blocking call has been made from the main thread and we've fired up a new thread for this function
Expand All @@ -293,10 +293,10 @@ int QgsBlockingProcess::run( QgsFeedback *feedback )
{ {
p.terminate(); p.terminate();
} ); } );
connect( &p, qgis::overload< int, QProcess::ExitStatus >::of( &QProcess::finished ), this, [&loop, &result, &status]( int res, QProcess::ExitStatus st ) connect( &p, qgis::overload< int, QProcess::ExitStatus >::of( &QProcess::finished ), this, [&loop, &result, &exitStatus]( int res, QProcess::ExitStatus st )
{ {
result = res; result = res;
status = st; exitStatus = st;
loop.quit(); loop.quit();
}, Qt::DirectConnection ); }, Qt::DirectConnection );


Expand Down Expand Up @@ -330,6 +330,12 @@ int QgsBlockingProcess::run( QgsFeedback *feedback )
runFunction(); runFunction();
} }


mExitStatus = exitStatus;
return result; return result;
}

QProcess::ExitStatus QgsBlockingProcess::exitStatus() const
{
return mExitStatus;
}; };


7 changes: 7 additions & 0 deletions src/core/qgsrunprocess.h
Expand Up @@ -172,13 +172,20 @@ class CORE_EXPORT QgsBlockingProcess : public QObject
*/ */
int run( QgsFeedback *feedback = nullptr ); int run( QgsFeedback *feedback = nullptr );


/**
* After a call to run(), returns the process' exit status.
*/
QProcess::ExitStatus exitStatus() const;

private: private:


QString mProcess; QString mProcess;
QStringList mArguments; QStringList mArguments;
std::function< void( const QByteArray & ) > mStdoutHandler; std::function< void( const QByteArray & ) > mStdoutHandler;
std::function< void( const QByteArray & ) > mStderrHandler; std::function< void( const QByteArray & ) > mStderrHandler;


QProcess::ExitStatus mExitStatus = QProcess::NormalExit;

}; };




Expand Down
40 changes: 36 additions & 4 deletions tests/src/python/test_qgsblockingprocess.py
Expand Up @@ -21,13 +21,15 @@
__date__ = 'January 2021' __date__ = 'January 2021'
__copyright__ = '(C) 2021, Nyall Dawson' __copyright__ = '(C) 2021, Nyall Dawson'


import qgis # NOQA import os
import tempfile


import qgis # NOQA
from qgis.PyQt.QtCore import QProcess
from qgis.core import ( from qgis.core import (
QgsBlockingProcess, QgsBlockingProcess,
QgsFeedback QgsFeedback
) )

from qgis.testing import unittest, start_app from qgis.testing import unittest, start_app


from utilities import unitTestDataPath from utilities import unitTestDataPath
Expand All @@ -40,7 +42,6 @@
class TestQgsBlockingProcess(unittest.TestCase): class TestQgsBlockingProcess(unittest.TestCase):


def test_process_ok(self): def test_process_ok(self):

def std_out(ba): def std_out(ba):
std_out.val += ba.data().decode('UTF-8') std_out.val += ba.data().decode('UTF-8')


Expand All @@ -57,11 +58,11 @@ def std_err(ba):


f = QgsFeedback() f = QgsFeedback()
self.assertEqual(p.run(f), 0) self.assertEqual(p.run(f), 0)
self.assertEqual(p.exitStatus(), QProcess.NormalExit)
self.assertIn('GDAL', std_out.val) self.assertIn('GDAL', std_out.val)
self.assertEqual(std_err.val, '') self.assertEqual(std_err.val, '')


def test_process_err(self): def test_process_err(self):

def std_out(ba): def std_out(ba):
std_out.val += ba.data().decode('UTF-8') std_out.val += ba.data().decode('UTF-8')


Expand All @@ -78,9 +79,40 @@ def std_err(ba):


f = QgsFeedback() f = QgsFeedback()
self.assertEqual(p.run(f), 1) self.assertEqual(p.run(f), 1)
self.assertEqual(p.exitStatus(), QProcess.NormalExit)
self.assertIn('Usage', std_out.val) self.assertIn('Usage', std_out.val)
self.assertIn('FAILURE', std_err.val) self.assertIn('FAILURE', std_err.val)


def test_process_crash(self):
"""
Test a script which simulates a crash
"""
temp_folder = tempfile.mkdtemp()

script_file = os.path.join(temp_folder, 'crash_process.sh')
with open(script_file, 'wt') as f:
f.write('kill $$')

os.chmod(script_file, 0o775)

def std_out(ba):
std_out.val += ba.data().decode('UTF-8')

std_out.val = ''

def std_err(ba):
std_err.val += ba.data().decode('UTF-8')

std_err.val = ''

p = QgsBlockingProcess('sh', [script_file])
p.setStdOutHandler(std_out)
p.setStdErrHandler(std_err)

f = QgsFeedback()
self.assertNotEqual(p.run(f), 0)
self.assertEqual(p.exitStatus(), QProcess.CrashExit)



if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

0 comments on commit ddf19ac

Please sign in to comment.