diff --git a/python/core/auto_generated/qgsrunprocess.sip.in b/python/core/auto_generated/qgsrunprocess.sip.in index a74f4dc6deb..4a8dc6a71a3 100644 --- a/python/core/auto_generated/qgsrunprocess.sip.in +++ b/python/core/auto_generated/qgsrunprocess.sip.in @@ -129,6 +129,13 @@ After execution completes, the process' result code will be returned. QProcess::ExitStatus exitStatus() const; %Docstring After a call to :py:func:`~QgsBlockingProcess.run`, returns the process' exit status. +%End + + QProcess::ProcessError processError() const; +%Docstring +After a call to :py:func:`~QgsBlockingProcess.run`, returns the process' reported error. + +Returns QProcess.UnknownError if no error occurred. %End }; diff --git a/python/plugins/processing/algs/gdal/GdalUtils.py b/python/plugins/processing/algs/gdal/GdalUtils.py index 6e74f157771..dce77829fc7 100644 --- a/python/plugins/processing/algs/gdal/GdalUtils.py +++ b/python/plugins/processing/algs/gdal/GdalUtils.py @@ -150,6 +150,8 @@ class GdalUtils: raise QgsProcessingException(GdalUtils.tr('Process was unexpectedly terminated')) elif res == 0: feedback.pushInfo(GdalUtils.tr('Process completed successfully')) + elif proc.processError() == QProcess.FailedToStart: + raise QgsProcessingException(GdalUtils.tr('Process {} failed to start. Either {} is missing, or you may have insufficient permissions to run the program.').format(command, command)) else: feedback.reportError(GdalUtils.tr('Process returned error code {}').format(res)) diff --git a/src/core/qgsrunprocess.cpp b/src/core/qgsrunprocess.cpp index 82b8f4c41ab..0a09e2bae31 100644 --- a/src/core/qgsrunprocess.cpp +++ b/src/core/qgsrunprocess.cpp @@ -272,8 +272,9 @@ int QgsBlockingProcess::run( QgsFeedback *feedback ) int result = 0; QProcess::ExitStatus exitStatus = QProcess::NormalExit; + QProcess::ProcessError error = QProcess::UnknownError; - std::function runFunction = [ this, &result, &exitStatus, feedback]() + std::function runFunction = [ this, &result, &exitStatus, &error, 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 @@ -319,8 +320,16 @@ int QgsBlockingProcess::run( QgsFeedback *feedback ) mStderrHandler( ba ); } ); p.start( mProcess, mArguments, QProcess::Unbuffered | QProcess::ReadWrite ); - - loop.exec(); + if ( !p.waitForStarted() ) + { + result = 1; + exitStatus = QProcess::NormalExit; + error = p.error(); + } + else + { + loop.exec(); + } mStdoutHandler( p.readAllStandardOutput() ); mStderrHandler( p.readAllStandardError() ); @@ -339,6 +348,7 @@ int QgsBlockingProcess::run( QgsFeedback *feedback ) } mExitStatus = exitStatus; + mProcessError = error; return result; } @@ -346,4 +356,9 @@ QProcess::ExitStatus QgsBlockingProcess::exitStatus() const { return mExitStatus; }; + +QProcess::ProcessError QgsBlockingProcess::processError() const +{ + return mProcessError; +}; #endif // QT_CONFIG(process) diff --git a/src/core/qgsrunprocess.h b/src/core/qgsrunprocess.h index 8e7bba4152c..94ed1852c05 100644 --- a/src/core/qgsrunprocess.h +++ b/src/core/qgsrunprocess.h @@ -185,6 +185,13 @@ class CORE_EXPORT QgsBlockingProcess : public QObject */ QProcess::ExitStatus exitStatus() const; + /** + * After a call to run(), returns the process' reported error. + * + * Returns QProcess::UnknownError if no error occurred. + */ + QProcess::ProcessError processError() const; + private: QString mProcess; @@ -193,6 +200,7 @@ class CORE_EXPORT QgsBlockingProcess : public QObject std::function< void( const QByteArray & ) > mStderrHandler; QProcess::ExitStatus mExitStatus = QProcess::NormalExit; + QProcess::ProcessError mProcessError = QProcess::UnknownError; }; #endif // QT_CONFIG(process) diff --git a/tests/src/python/test_qgsblockingprocess.py b/tests/src/python/test_qgsblockingprocess.py index 20e711d444f..459c1c526f4 100644 --- a/tests/src/python/test_qgsblockingprocess.py +++ b/tests/src/python/test_qgsblockingprocess.py @@ -113,6 +113,31 @@ class TestQgsBlockingProcess(unittest.TestCase): self.assertNotEqual(p.run(f), 0) self.assertEqual(p.exitStatus(), QProcess.CrashExit) + def test_process_no_file(self): + """ + Test a script which doesn't exist + """ + + 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 = '' + + # this program definitely doesn't exist! + p = QgsBlockingProcess('qgis_sucks', ['--version']) + p.setStdOutHandler(std_out) + p.setStdErrHandler(std_err) + + f = QgsFeedback() + self.assertEqual(p.run(f), 1) + self.assertEqual(p.exitStatus(), QProcess.NormalExit) + self.assertEqual(p.processError(), QProcess.FailedToStart) + def test_process_env(self): """ Test that process inherits system environment correctly