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.

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

};
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();

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,
// 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();
} );
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;
status = st;
exitStatus = st;
loop.quit();
}, Qt::DirectConnection );

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

mExitStatus = exitStatus;
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 );

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

private:

QString mProcess;
QStringList mArguments;
std::function< void( const QByteArray & ) > mStdoutHandler;
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'
__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 (
QgsBlockingProcess,
QgsFeedback
)

from qgis.testing import unittest, start_app

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

def test_process_ok(self):

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

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

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

def test_process_err(self):

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

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

f = QgsFeedback()
self.assertEqual(p.run(f), 1)
self.assertEqual(p.exitStatus(), QProcess.NormalExit)
self.assertIn('Usage', std_out.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__':
unittest.main()

0 comments on commit ddf19ac

Please sign in to comment.