From 43d5da83e4fb3d7f9e4a7088365e98eb69a4df3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Thu, 6 Jan 2022 11:49:36 +0100 Subject: [PATCH] fix: utils tests on macOS test_utils tests were failing on macOS when the settings file was properly defined and present. Close #560. --- tests/test_utils.py | 60 +++++++++++++++++++++++++++++++++++++---- tutor/commands/local.py | 12 ++++----- tutor/utils.py | 27 ++++++++++--------- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 73e11bf..76d81da 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,7 +4,7 @@ import sys import tempfile import unittest from io import StringIO -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, mock_open, patch from tutor import exceptions, utils @@ -152,9 +152,59 @@ class UtilsTests(unittest.TestCase): process.kill.assert_called_once() @patch("sys.platform", "win32") - def test_check_macos_memory_win32_should_skip(self) -> None: - utils.check_macos_memory() + def test_check_macos_docker_memory_win32_should_skip(self) -> None: + utils.check_macos_docker_memory() @patch("sys.platform", "darwin") - def test_check_macos_memory_darwin_filenotfound(self) -> None: - self.assertRaises(exceptions.TutorError, utils.check_macos_memory) + def test_check_macos_docker_memory_darwin(self) -> None: + with patch("tutor.utils.open", mock_open(read_data='{"memoryMiB": 4096}')): + utils.check_macos_docker_memory() + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_filenotfound(self) -> None: + with patch("tutor.utils.open", mock_open()) as mock_open_settings: + mock_open_settings.return_value.__enter__.side_effect = FileNotFoundError + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertIn("Error accessing Docker settings file", e.exception.args[0]) + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_json_decode_error(self) -> None: + with patch("tutor.utils.open", mock_open(read_data="invalid")): + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertIn("invalid JSON", e.exception.args[0]) + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_key_error(self) -> None: + with patch("tutor.utils.open", mock_open(read_data="{}")): + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertIn("key 'memoryMiB' not found", e.exception.args[0]) + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_type_error(self) -> None: + with patch( + "tutor.utils.open", mock_open(read_data='{"memoryMiB": "invalidstring"}') + ): + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertIn("Unexpected JSON data", e.exception.args[0]) + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_insufficient_memory(self) -> None: + with patch("tutor.utils.open", mock_open(read_data='{"memoryMiB": 4095}')): + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertEqual( + "Docker is configured to allocate 4095 MiB RAM, less than the recommended 4096 MiB", + e.exception.args[0], + ) + + @patch("sys.platform", "darwin") + def test_check_macos_docker_memory_darwin_encoding_error(self) -> None: + with patch("tutor.utils.open", mock_open()) as mock_open_settings: + mock_open_settings.return_value.__enter__.side_effect = TypeError + with self.assertRaises(exceptions.TutorError) as e: + utils.check_macos_docker_memory() + self.assertIn("Text encoding error", e.exception.args[0]) diff --git a/tutor/commands/local.py b/tutor/commands/local.py index f1570b8..49da6d9 100644 --- a/tutor/commands/local.py +++ b/tutor/commands/local.py @@ -45,16 +45,14 @@ def local(context: click.Context) -> None: @click.pass_context def quickstart(context: click.Context, non_interactive: bool, pullimages: bool) -> None: try: - utils.check_macos_memory() + utils.check_macos_docker_memory() except exceptions.TutorError as e: fmt.echo_alert( - """Could not verify sufficient RAM allocation in Docker: - {} -Tutor may not work if Docker is configured with < 4 GB RAM. Please follow instructions from: + f"""Could not verify sufficient RAM allocation in Docker: + {e} +Tutor may not work if Docker is configured with < 4 GB RAM. Please follow the instructions from: https://docs.tutor.overhang.io/install.html - """.format( - str(e) - ) + """ ) if tutor_env.needs_major_upgrade(context.obj.root): diff --git a/tutor/utils.py b/tutor/utils.py index 41df184..4a59778 100644 --- a/tutor/utils.py +++ b/tutor/utils.py @@ -222,9 +222,12 @@ def check_output(*command: str) -> bytes: ) from e -def check_macos_memory() -> None: +def check_macos_docker_memory() -> None: """ Try to check that the RAM allocated to the Docker VM on macOS is at least 4 GB. + + Parse macOS Docker settings file from user directory and return the max + allocated memory. Will raise TutorError in case of parsing/loading error. """ if sys.platform != "darwin": return @@ -238,27 +241,25 @@ def check_macos_memory() -> None: data = json.load(fp) memory_mib = int(data["memoryMiB"]) except OSError as e: - raise exceptions.TutorError( - "Error accessing {}: [{}] {}".format(settings_path, e.errno, e.strerror) - ) from e + raise exceptions.TutorError(f"Error accessing Docker settings file: {e}") from e except json.JSONDecodeError as e: raise exceptions.TutorError( - "Error reading {}: invalid JSON: {} [{}:{}]".format( - settings_path, e.msg, e.lineno, e.colno - ) + f"Error reading {settings_path}, invalid JSON: {e}" ) from e - except (ValueError, TypeError, OverflowError) as e: - # ValueError from open() indicates an encoding error + except ValueError as e: raise exceptions.TutorError( - "Text encoding error or unexpected JSON data: in {}: {}".format( - settings_path, str(e) - ) + f"Unexpected JSON data in {settings_path}: {e}" ) from e except KeyError as e: # Value is absent (Docker creates the file with the default setting of 2048 explicitly # written in, so we shouldn't need to assume a default value here.) raise exceptions.TutorError( - "key 'memoryMiB' not found in {}".format(settings_path) + f"key 'memoryMiB' not found in {settings_path}" + ) from e + except (TypeError, OverflowError) as e: + # TypeError from open() indicates an encoding error + raise exceptions.TutorError( + f"Text encoding error in {settings_path}: {e}" ) from e if memory_mib < 4096: