From 8dd56ff4617ea7d3412ef48a46b00ae85467d8d2 Mon Sep 17 00:00:00 2001 From: Dave Dalcino Date: Mon, 13 Mar 2023 17:47:09 -0700 Subject: [PATCH] Catch OSError(errno.ENOSPC) and PermissionError When the destination drive is not writable or has insufficient space, these exceptions are raised during `run_installer`. Unless they are caught and dealt with, `aqtinstall` requests that the user file a bug report. This change catches these errors and prints a nice error message instead of requesting a bug report. --- aqt/exceptions.py | 8 ++++++++ aqt/installer.py | 20 ++++++++++++++++++++ tests/test_install.py | 34 +++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/aqt/exceptions.py b/aqt/exceptions.py index 012a8c8..972488c 100644 --- a/aqt/exceptions.py +++ b/aqt/exceptions.py @@ -102,3 +102,11 @@ class UpdaterError(AqtException): class OutOfMemory(AqtException): pass + + +class OutOfDiskSpace(AqtException): + pass + + +class DiskAccessNotPermitted(AqtException): + pass diff --git a/aqt/installer.py b/aqt/installer.py index 4af89c8..a27fd2e 100644 --- a/aqt/installer.py +++ b/aqt/installer.py @@ -22,6 +22,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import argparse +import errno import gc import multiprocessing import os @@ -47,6 +48,8 @@ from aqt.exceptions import ( ArchiveListError, CliInputError, CliKeyboardInterrupt, + DiskAccessNotPermitted, + OutOfDiskSpace, OutOfMemory, ) from aqt.helper import ( @@ -1127,6 +1130,23 @@ def run_installer(archives: List[QtPackage], base_dir: str, sevenzip: Optional[s pool.starmap(installer, tasks) pool.close() pool.join() + except PermissionError as e: # subclass of OSError + close_worker_pool_on_exception(e) + raise DiskAccessNotPermitted( + f"Failed to write to base directory at {base_dir}", + suggested_action=[ + "Check that the destination is writable and does not already contain files owned by another user." + ], + ) from e + except OSError as e: + close_worker_pool_on_exception(e) + if e.errno == errno.ENOSPC: + raise OutOfDiskSpace( + "Insufficient disk space to complete installation.", + suggested_action=["Check available disk space.", "Check size requirements for installation."], + ) from e + else: + raise except KeyboardInterrupt as e: close_worker_pool_on_exception(e) raise CliKeyboardInterrupt("Installer halted by keyboard interrupt.") from e diff --git a/tests/test_install.py b/tests/test_install.py index 81a650a..b3a3158 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1,3 +1,4 @@ +import errno import hashlib import logging import os @@ -1156,10 +1157,10 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option @pytest.mark.parametrize( - "exception_class, settings_file, expect_end_msg, expect_return", + "exception, settings_file, expect_end_msg, expect_return", ( ( - RuntimeError, + RuntimeError(), "../aqt/settings.ini", "===========================PLEASE FILE A BUG REPORT===========================\n" "You have discovered a bug in aqt.\n" @@ -1168,14 +1169,14 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option Cli.UNHANDLED_EXCEPTION_CODE, ), ( - KeyboardInterrupt, + KeyboardInterrupt(), "../aqt/settings.ini", "WARNING : Caught KeyboardInterrupt, terminating installer workers\n" "ERROR : Installer halted by keyboard interrupt.", 1, ), ( - MemoryError, + MemoryError(), "../aqt/settings.ini", "WARNING : Caught MemoryError, terminating installer workers\n" "ERROR : Out of memory when downloading and extracting archives in parallel.\n" @@ -1187,7 +1188,7 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option 1, ), ( - MemoryError, + MemoryError(), "data/settings_no_concurrency.ini", "WARNING : Caught MemoryError, terminating installer workers\n" "ERROR : Out of memory when downloading and extracting archives.\n" @@ -1197,11 +1198,30 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option "(see https://aqtinstall.readthedocs.io/en/latest/cli.html#cmdoption-list-tool-external)", 1, ), + ( + OSError(errno.ENOSPC, "No space left on device"), + "../aqt/settings.ini", + "WARNING : Caught OSError, terminating installer workers\n" + "ERROR : Insufficient disk space to complete installation.\n" + "==============================Suggested follow-up:==============================\n" + "* Check available disk space.\n" + "* Check size requirements for installation.", + 1, + ), + ( + PermissionError(), + "../aqt/settings.ini", + "WARNING : Caught PermissionError, terminating installer workers\n" + f"ERROR : Failed to write to base directory at {os.getcwd()}\n" + "==============================Suggested follow-up:==============================\n" + "* Check that the destination is writable and does not already contain files owned by another user.", + 1, + ), ), ) -def test_install_pool_exception(monkeypatch, capsys, exception_class, settings_file, expect_end_msg, expect_return): +def test_install_pool_exception(monkeypatch, capsys, exception, settings_file, expect_end_msg, expect_return): def mock_installer_func(*args): - raise exception_class() + raise exception host, target, ver, arch = "windows", "desktop", "6.1.0", "win64_mingw81" updates_url = "windows_x86/desktop/qt6_610/Updates.xml"