From 0193193a5de50c60f6c0c645b5dce2c323fc410d Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Tue, 29 Apr 2025 13:50:11 -0400 Subject: [PATCH 01/13] HelpersTask615: Add unittest for docker_build_local_image() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 helpers/test/test_lib_tasks_docker_release.py diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py new file mode 100644 index 000000000..00d95144a --- /dev/null +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -0,0 +1,176 @@ +import logging +import re +import unittest.mock as umock + +import helpers.hsystem as hsystem +import helpers.hunit_test as hunitest +import helpers.lib_tasks_docker_release as hltadore + +_LOG = logging.getLogger(__name__) + + +# ############################################################################# +# TestDockerBuildLocalImage1 +# ############################################################################# + + +class TestDockerBuildLocalImage1(hunitest.TestCase): + """ + Test suite for docker_build_local_image function. + + This class contains unit tests for both single-architecture and + multi-architecture Docker image builds. + """ + + def setUp(self) -> None: + """ + Set up test environment and initialize all necessary mocks. + """ + # Mock system calls. + self.system_patcher = umock.patch("helpers.hsystem.system") + self.mock_system = self.system_patcher.start() + # Mock run. + self.run_patcher = umock.patch("helpers.lib_tasks_utils.run") + self.mock_run = self.run_patcher.start() + # Mock version validation. + self.version_patcher = umock.patch( + "helpers.lib_tasks_docker.dassert_is_subsequent_version" + ) + self.mock_version = self.version_patcher.start() + # Mock docker login + self.docker_login_patcher = umock.patch( + "helpers.lib_tasks_docker.docker_login" + ) + self.mock_docker_login = self.docker_login_patcher.start() + # Get user name. + self.user = hsystem.get_user_name() + + def tearDown(self) -> None: + """ + Clean up test environment. + + This method stops all mocks after each test case. + """ + self.system_patcher.stop() + self.run_patcher.stop() + self.version_patcher.stop() + self.docker_login_patcher.stop() + + def test_docker_build_single_arch(self) -> None: + """ + Test building a local Docker image with single architecture. + + This test verifies that the correct sequence of commands is + generated for building a local Docker image with single + architecture. + """ + # Prepare inputs. + mock_ctx = umock.MagicMock() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_multi_arch = "" + # Call tested function. + hltadore.docker_build_local_image.body( + ctx=mock_ctx, + version=test_version, + base_image=test_base_image, + multi_arch=test_multi_arch, + poetry_mode="update", + cache=False, + ) + # Extract the command argument from the call. + actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] + # Check output. + expected_cmds = [ + "cp -f devops/docker_build/dockerignore.dev .dockerignore", + "tar -czh . | DOCKER_BUILDKIT=0 time docker build " + " --no-cache " + " --build-arg AM_CONTAINER_VERSION=1.0.0 " + " --build-arg INSTALL_DIND=True " + " --build-arg POETRY_MODE=update " + " --build-arg CLEAN_UP_INSTALLATION=True " + f" --tag {test_base_image}:local-{self.user}-1.0.0 " + " --file devops/docker_build/dev.Dockerfile -", + "invoke docker_cmd " + "--stage local " + f"--version {test_version} " + f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", + "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", + "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", + f"docker image ls {test_base_image}:local-{self.user}-1.0.0", + ] + # Normalize both expected and actual commands. + expected = [self._normalize_command(cmd) for cmd in expected_cmds] + actual = [self._normalize_command(cmd) for cmd in actual_cmds] + self.assertEqual(expected, actual) + + def test_docker_build_multi_arch(self) -> None: + """ + Test building a local Docker image with multiple architectures. + + This test verifies that the correct sequence of commands is + generated for building a local Docker image with multiple + architectures. + """ + # Prepare inputs. + mock_ctx = umock.MagicMock() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_multi_arch = "linux/amd64,linux/arm64" + # Call tested function. + hltadore.docker_build_local_image.body( + ctx=mock_ctx, + version=test_version, + base_image=test_base_image, + multi_arch=test_multi_arch, + poetry_mode="update", + cache=False, + ) + # Extract the command argument from the call. + actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] + # Check output. + expected_cmds = [ + "cp -f devops/docker_build/dockerignore.dev .dockerignore", + "docker buildx create --name multiarch_builder --driver docker-container --bootstrap && docker buildx use multiarch_builder", + "tar -czh . | DOCKER_BUILDKIT=0 time docker buildx build " + " --no-cache " + " --push --platform linux/amd64,linux/arm64 " + " --build-arg AM_CONTAINER_VERSION=1.0.0 " + " --build-arg INSTALL_DIND=True " + " --build-arg POETRY_MODE=update " + " --build-arg CLEAN_UP_INSTALLATION=True " + f" --tag {test_base_image}:local-{self.user}-1.0.0 " + " --file devops/docker_build/dev.Dockerfile -", + f"docker pull {test_base_image}:local-{self.user}-1.0.0", + "invoke docker_cmd " + "--stage local " + f"--version {test_version} " + f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", + "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", + "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", + f"docker image ls {test_base_image}:local-{self.user}-1.0.0", + ] + # Normalize both expected and actual commands. + expected = [self._normalize_command(cmd) for cmd in expected_cmds] + actual = [self._normalize_command(cmd) for cmd in actual_cmds] + self.assertEqual(expected, actual) + + def _normalize_command(self, cmd: str) -> str: + """ + Normalize a command string by removing extra whitespace and line + continuations. + + :param cmd: command string to normalize + :return: normalized command string + """ + # Replace line continuation backslashes with spaces. + cmd = re.sub(r"\\\s*\n\s*", " ", cmd) + # Remove all extra whitespaces. + cmd = re.sub(r"\s+", " ", cmd) + # Remove spaces around special characters. + chars_to_normalize = ["=", ",", "|"] + escaped_chars = "".join(re.escape(ch) for ch in chars_to_normalize) + cmd = re.sub(rf"\s*([{escaped_chars}])\s*", r"\1", cmd) + # Remove leading/trailing whitespace. + cmd = cmd.strip() + return cmd From 070c1c1003e39c9dbc0f818c478cc1f87b9c4b31 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Tue, 29 Apr 2025 14:09:22 -0400 Subject: [PATCH 02/13] HelpersTask615: Modify comments and docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 00d95144a..7ac6a19da 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -15,12 +15,6 @@ class TestDockerBuildLocalImage1(hunitest.TestCase): - """ - Test suite for docker_build_local_image function. - - This class contains unit tests for both single-architecture and - multi-architecture Docker image builds. - """ def setUp(self) -> None: """ @@ -37,19 +31,16 @@ def setUp(self) -> None: "helpers.lib_tasks_docker.dassert_is_subsequent_version" ) self.mock_version = self.version_patcher.start() - # Mock docker login + # Mock docker login. self.docker_login_patcher = umock.patch( "helpers.lib_tasks_docker.docker_login" ) self.mock_docker_login = self.docker_login_patcher.start() - # Get user name. self.user = hsystem.get_user_name() def tearDown(self) -> None: """ - Clean up test environment. - - This method stops all mocks after each test case. + Clean up test environment by stopping all mocks after each test case. """ self.system_patcher.stop() self.run_patcher.stop() @@ -78,7 +69,7 @@ def test_docker_build_single_arch(self) -> None: poetry_mode="update", cache=False, ) - # Extract the command argument from the call. + # Extract the command arguments from the call. actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] # Check output. expected_cmds = [ @@ -102,7 +93,11 @@ def test_docker_build_single_arch(self) -> None: # Normalize both expected and actual commands. expected = [self._normalize_command(cmd) for cmd in expected_cmds] actual = [self._normalize_command(cmd) for cmd in actual_cmds] - self.assertEqual(expected, actual) + self.assertEqual( + expected, + actual, + f"Expected commands: {expected}\nActual commands: {actual}", + ) def test_docker_build_multi_arch(self) -> None: """ @@ -126,7 +121,7 @@ def test_docker_build_multi_arch(self) -> None: poetry_mode="update", cache=False, ) - # Extract the command argument from the call. + # Extract the command arguments from the call. actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] # Check output. expected_cmds = [ @@ -153,11 +148,15 @@ def test_docker_build_multi_arch(self) -> None: # Normalize both expected and actual commands. expected = [self._normalize_command(cmd) for cmd in expected_cmds] actual = [self._normalize_command(cmd) for cmd in actual_cmds] - self.assertEqual(expected, actual) + self.assertEqual( + expected, + actual, + f"Expected commands: {expected}\nActual commands: {actual}", + ) def _normalize_command(self, cmd: str) -> str: """ - Normalize a command string by removing extra whitespace and line + Normalize a command string by removing whitespace and line continuations. :param cmd: command string to normalize From 62e1f7e3247bd6b4aa13a4452731793ada4d1430 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Tue, 29 Apr 2025 23:00:29 -0400 Subject: [PATCH 03/13] HelpersTask615: Move helper functions out of the class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 107 +++++++++++------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 7ac6a19da..32fc8ca98 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,6 +1,7 @@ import logging import re import unittest.mock as umock +from typing import List, Tuple import helpers.hsystem as hsystem import helpers.hunit_test as hunitest @@ -9,6 +10,41 @@ _LOG = logging.getLogger(__name__) +def normalize_command(cmd: str) -> str: + """ + Normalize a command string by removing line continuations and spaces around + special characters. + + :param cmd: command string to normalize + :return: normalized command string + """ + cmd = re.sub(r"\\\s*\n\s*", " ", cmd) + chars_to_normalize = ["=", ",", "|"] + escaped_chars = "".join(re.escape(ch) for ch in chars_to_normalize) + cmd = re.sub(rf"\s*([{escaped_chars}])\s*", r"\1", cmd) + return cmd + + +def convert_commands_to_strings( + actual_cmds: List[str], expected_cmds: List[str] +) -> Tuple[str, str]: + """ + Convert a list of commands to strings and normalize them. + + :param actual_cmds: list of actual command strings + :param expected_cmds: list of expected command strings + :return: normalized actual commands string and normalized expected + commands string + """ + # Normalize each command in both lists. + actual_normalized = [normalize_command(cmd) for cmd in actual_cmds] + expected_normalized = [normalize_command(cmd) for cmd in expected_cmds] + # Convert to strings. + actual_str = "\n".join(actual_normalized) + expected_str = "\n".join(expected_normalized) + return actual_str, expected_str + + # ############################################################################# # TestDockerBuildLocalImage1 # ############################################################################# @@ -20,6 +56,7 @@ def setUp(self) -> None: """ Set up test environment and initialize all necessary mocks. """ + super().setUp() # Mock system calls. self.system_patcher = umock.patch("helpers.hsystem.system") self.mock_system = self.system_patcher.start() @@ -36,6 +73,7 @@ def setUp(self) -> None: "helpers.lib_tasks_docker.docker_login" ) self.mock_docker_login = self.docker_login_patcher.start() + # self.user = hsystem.get_user_name() def tearDown(self) -> None: @@ -46,6 +84,7 @@ def tearDown(self) -> None: self.run_patcher.stop() self.version_patcher.stop() self.docker_login_patcher.stop() + super().tearDown() def test_docker_build_single_arch(self) -> None: """ @@ -59,15 +98,14 @@ def test_docker_build_single_arch(self) -> None: mock_ctx = umock.MagicMock() test_version = "1.0.0" test_base_image = "test-registry.com/test-image" - test_multi_arch = "" - # Call tested function. + # Call tested function using `.body` to bypass invoke's argument + # parsing and run the raw implementation. hltadore.docker_build_local_image.body( - ctx=mock_ctx, - version=test_version, + mock_ctx, + test_version, + cache=False, base_image=test_base_image, - multi_arch=test_multi_arch, poetry_mode="update", - cache=False, ) # Extract the command arguments from the call. actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] @@ -90,13 +128,14 @@ def test_docker_build_single_arch(self) -> None: "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] - # Normalize both expected and actual commands. - expected = [self._normalize_command(cmd) for cmd in expected_cmds] - actual = [self._normalize_command(cmd) for cmd in actual_cmds] - self.assertEqual( - expected, + # Normalize and convert both command lists to strings. + actual, expected = convert_commands_to_strings(actual_cmds, expected_cmds) + self.assert_equal( actual, - f"Expected commands: {expected}\nActual commands: {actual}", + expected, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, ) def test_docker_build_multi_arch(self) -> None: @@ -112,14 +151,15 @@ def test_docker_build_multi_arch(self) -> None: test_version = "1.0.0" test_base_image = "test-registry.com/test-image" test_multi_arch = "linux/amd64,linux/arm64" - # Call tested function. + # Call tested function using `.body` to bypass invoke's argument + # parsing and run the raw implementation. hltadore.docker_build_local_image.body( - ctx=mock_ctx, - version=test_version, + mock_ctx, + test_version, + cache=False, base_image=test_base_image, - multi_arch=test_multi_arch, poetry_mode="update", - cache=False, + multi_arch=test_multi_arch, ) # Extract the command arguments from the call. actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] @@ -145,31 +185,12 @@ def test_docker_build_multi_arch(self) -> None: "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] - # Normalize both expected and actual commands. - expected = [self._normalize_command(cmd) for cmd in expected_cmds] - actual = [self._normalize_command(cmd) for cmd in actual_cmds] - self.assertEqual( - expected, + # Normalize and convert both command lists to strings. + actual, expected = convert_commands_to_strings(actual_cmds, expected_cmds) + self.assert_equal( actual, - f"Expected commands: {expected}\nActual commands: {actual}", + expected, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, ) - - def _normalize_command(self, cmd: str) -> str: - """ - Normalize a command string by removing whitespace and line - continuations. - - :param cmd: command string to normalize - :return: normalized command string - """ - # Replace line continuation backslashes with spaces. - cmd = re.sub(r"\\\s*\n\s*", " ", cmd) - # Remove all extra whitespaces. - cmd = re.sub(r"\s+", " ", cmd) - # Remove spaces around special characters. - chars_to_normalize = ["=", ",", "|"] - escaped_chars = "".join(re.escape(ch) for ch in chars_to_normalize) - cmd = re.sub(rf"\s*([{escaped_chars}])\s*", r"\1", cmd) - # Remove leading/trailing whitespace. - cmd = cmd.strip() - return cmd From 3dc176bc5ce4999767d14ec1d1088cd62472b8ea Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Tue, 29 Apr 2025 23:06:04 -0400 Subject: [PATCH 04/13] HelpersTask615: Rename helper functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 32fc8ca98..42b9c8d95 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -10,7 +10,7 @@ _LOG = logging.getLogger(__name__) -def normalize_command(cmd: str) -> str: +def _normalize_command(cmd: str) -> str: """ Normalize a command string by removing line continuations and spaces around special characters. @@ -25,7 +25,7 @@ def normalize_command(cmd: str) -> str: return cmd -def convert_commands_to_strings( +def _convert_commands_to_strings( actual_cmds: List[str], expected_cmds: List[str] ) -> Tuple[str, str]: """ @@ -37,8 +37,8 @@ def convert_commands_to_strings( commands string """ # Normalize each command in both lists. - actual_normalized = [normalize_command(cmd) for cmd in actual_cmds] - expected_normalized = [normalize_command(cmd) for cmd in expected_cmds] + actual_normalized = [_normalize_command(cmd) for cmd in actual_cmds] + expected_normalized = [_normalize_command(cmd) for cmd in expected_cmds] # Convert to strings. actual_str = "\n".join(actual_normalized) expected_str = "\n".join(expected_normalized) @@ -129,7 +129,9 @@ def test_docker_build_single_arch(self) -> None: f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] # Normalize and convert both command lists to strings. - actual, expected = convert_commands_to_strings(actual_cmds, expected_cmds) + actual, expected = _convert_commands_to_strings( + actual_cmds, expected_cmds + ) self.assert_equal( actual, expected, @@ -186,7 +188,9 @@ def test_docker_build_multi_arch(self) -> None: f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] # Normalize and convert both command lists to strings. - actual, expected = convert_commands_to_strings(actual_cmds, expected_cmds) + actual, expected = _convert_commands_to_strings( + actual_cmds, expected_cmds + ) self.assert_equal( actual, expected, From 49c5841a8a04aeba72cb6c750e57d6e9e6d65f46 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Wed, 30 Apr 2025 16:19:32 -0400 Subject: [PATCH 05/13] HelpersTask615: Split the check into seperate checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 112 ++++++++++++++---- 1 file changed, 90 insertions(+), 22 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 42b9c8d95..61e7d5b5e 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -6,6 +6,7 @@ import helpers.hsystem as hsystem import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore +import helpers.test.test_lib_tasks as httestlib _LOG = logging.getLogger(__name__) @@ -45,6 +46,19 @@ def _convert_commands_to_strings( return actual_str, expected_str +def _extract_commands_from_call(calls: List[umock._Call]) -> List[str]: + """ + Extract command strings from a list of mock call arguments. + + :param calls: list of mock call objects containing (args, kwargs) + :return: list of command strings + """ + # Each mock call is a (args, kwargs) tuple, extract the command string + # from args[1] in each call. + call_list = [call[0][1] for call in calls] + return call_list + + # ############################################################################# # TestDockerBuildLocalImage1 # ############################################################################# @@ -95,7 +109,7 @@ def test_docker_build_single_arch(self) -> None: architecture. """ # Prepare inputs. - mock_ctx = umock.MagicMock() + mock_ctx = httestlib._build_mock_context_returning_ok() test_version = "1.0.0" test_base_image = "test-registry.com/test-image" # Call tested function using `.body` to bypass invoke's argument @@ -107,10 +121,10 @@ def test_docker_build_single_arch(self) -> None: base_image=test_base_image, poetry_mode="update", ) - # Extract the command arguments from the call. - actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] - # Check output. - expected_cmds = [ + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + # Check build image commands. + build_cmds = actual_cmds[:2] + expected_build_cmds = [ "cp -f devops/docker_build/dockerignore.dev .dockerignore", "tar -czh . | DOCKER_BUILDKIT=0 time docker build " " --no-cache " @@ -120,21 +134,48 @@ def test_docker_build_single_arch(self) -> None: " --build-arg CLEAN_UP_INSTALLATION=True " f" --tag {test_base_image}:local-{self.user}-1.0.0 " " --file devops/docker_build/dev.Dockerfile -", + ] + actual_build, expected_build = _convert_commands_to_strings( + build_cmds, expected_build_cmds + ) + self.assert_equal( + actual_build, + expected_build, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + # Check poetry file commands. + poetry_cmds = actual_cmds[2:5] + expected_poetry_cmds = [ "invoke docker_cmd " "--stage local " f"--version {test_version} " f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", - f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] - # Normalize and convert both command lists to strings. - actual, expected = _convert_commands_to_strings( - actual_cmds, expected_cmds + actual_poetry, expected_poetry = _convert_commands_to_strings( + poetry_cmds, expected_poetry_cmds ) self.assert_equal( - actual, - expected, + actual_poetry, + expected_poetry, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + # Check list images command. + actual_list_cmd = actual_cmds[5] + expected_list_cmd = ( + f"docker image ls {test_base_image}:local-{self.user}-1.0.0" + ) + actual_list, expected_list = _convert_commands_to_strings( + actual_list_cmd, expected_list_cmd + ) + self.assert_equal( + actual_list, + expected_list, fuzzy_match=True, remove_lead_trail_empty_lines=True, dedent=True, @@ -149,7 +190,7 @@ def test_docker_build_multi_arch(self) -> None: architectures. """ # Prepare inputs. - mock_ctx = umock.MagicMock() + mock_ctx = httestlib._build_mock_context_returning_ok() test_version = "1.0.0" test_base_image = "test-registry.com/test-image" test_multi_arch = "linux/amd64,linux/arm64" @@ -163,10 +204,10 @@ def test_docker_build_multi_arch(self) -> None: poetry_mode="update", multi_arch=test_multi_arch, ) - # Extract the command arguments from the call. - actual_cmds = [call[0][1] for call in self.mock_run.call_args_list] - # Check output. - expected_cmds = [ + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + # Check buildx setup and build commands. + actual_build_cmds = actual_cmds[:3] + expected_build_cmds = [ "cp -f devops/docker_build/dockerignore.dev .dockerignore", "docker buildx create --name multiarch_builder --driver docker-container --bootstrap && docker buildx use multiarch_builder", "tar -czh . | DOCKER_BUILDKIT=0 time docker buildx build " @@ -178,6 +219,20 @@ def test_docker_build_multi_arch(self) -> None: " --build-arg CLEAN_UP_INSTALLATION=True " f" --tag {test_base_image}:local-{self.user}-1.0.0 " " --file devops/docker_build/dev.Dockerfile -", + ] + actual_build, expected_build = _convert_commands_to_strings( + actual_build_cmds, expected_build_cmds + ) + self.assert_equal( + actual_build, + expected_build, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + # Check pull and poetry file commands. + actual_poetry_cmds = actual_cmds[3:7] + expected_poetry_cmds = [ f"docker pull {test_base_image}:local-{self.user}-1.0.0", "invoke docker_cmd " "--stage local " @@ -185,15 +240,28 @@ def test_docker_build_multi_arch(self) -> None: f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", - f"docker image ls {test_base_image}:local-{self.user}-1.0.0", ] - # Normalize and convert both command lists to strings. - actual, expected = _convert_commands_to_strings( - actual_cmds, expected_cmds + actual_poetry, expected_poetry = _convert_commands_to_strings( + actual_poetry_cmds, expected_poetry_cmds + ) + self.assert_equal( + actual_poetry, + expected_poetry, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + # Check list images command. + actual_list_cmd = actual_cmds[7] + expected_list_cmd = ( + f"docker image ls {test_base_image}:local-{self.user}-1.0.0" + ) + actual_list, expected_list = _convert_commands_to_strings( + actual_list_cmd, expected_list_cmd ) self.assert_equal( - actual, - expected, + actual_list, + expected_list, fuzzy_match=True, remove_lead_trail_empty_lines=True, dedent=True, From 1da532ae93d05f8b5638e007b020189a52785117 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Thu, 1 May 2025 10:59:27 -0400 Subject: [PATCH 06/13] HelpersTask615: Remove .body from function call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 61e7d5b5e..4a0370f8e 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -112,9 +112,8 @@ def test_docker_build_single_arch(self) -> None: mock_ctx = httestlib._build_mock_context_returning_ok() test_version = "1.0.0" test_base_image = "test-registry.com/test-image" - # Call tested function using `.body` to bypass invoke's argument - # parsing and run the raw implementation. - hltadore.docker_build_local_image.body( + # Call tested function . + hltadore.docker_build_local_image( mock_ctx, test_version, cache=False, @@ -194,9 +193,8 @@ def test_docker_build_multi_arch(self) -> None: test_version = "1.0.0" test_base_image = "test-registry.com/test-image" test_multi_arch = "linux/amd64,linux/arm64" - # Call tested function using `.body` to bypass invoke's argument - # parsing and run the raw implementation. - hltadore.docker_build_local_image.body( + # Call tested function. + hltadore.docker_build_local_image( mock_ctx, test_version, cache=False, From e61b80239d7eb228812f6d2ada492d74bcc017a0 Mon Sep 17 00:00:00 2001 From: GP Saggese Date: Thu, 1 May 2025 11:20:03 -0400 Subject: [PATCH 07/13] Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/hunit_test.py | 5 +- helpers/test/test_lib_tasks.py | 4 +- helpers/test/test_lib_tasks_docker_release.py | 74 ++++++------------- 3 files changed, 26 insertions(+), 57 deletions(-) diff --git a/helpers/hunit_test.py b/helpers/hunit_test.py index 8e2259d54..c4fd69078 100644 --- a/helpers/hunit_test.py +++ b/helpers/hunit_test.py @@ -396,7 +396,10 @@ def purify_from_environment(txt: str) -> str: pass # 2) Remove CSFY_GIT_ROOT_PATH val = os.environ.get("CSFY_HOST_GIT_ROOT_PATH") - txt = re.sub(val, "$CSFY_HOST_GIT_ROOT_PATH", txt, flags=re.MULTILINE) + if val is not None: + txt = re.sub(val, "$CSFY_HOST_GIT_ROOT_PATH", txt, flags=re.MULTILINE) + else: + _LOG.debug("CSFY_HOST_GIT_ROOT_PATH is not set") # 3) Replace the path of current working dir with `$PWD`. pwd = os.getcwd() pattern = re.compile(f"{pwd}{dir_pattern}") diff --git a/helpers/test/test_lib_tasks.py b/helpers/test/test_lib_tasks.py index a33893e2c..04ee72d3a 100644 --- a/helpers/test/test_lib_tasks.py +++ b/helpers/test/test_lib_tasks.py @@ -67,6 +67,7 @@ def tear_down_test(self) -> None: # ############################################################################# +# TODO(gp): Make it public. def _build_mock_context_returning_ok() -> invoke.MockContext: """ Build a MockContext catching any command and returning rc=0. @@ -110,9 +111,6 @@ def _check_output(self, target: str, check: bool = True) -> None: self._check_calls(ctx) -# ############################################################################# - - # TODO(gp): We should group the tests by what is tested and not how it's # tested. E.g. TestDryRunTasks1::test_print_setup and # TestDryRunTasks2::test_print_setup should go together in a class. diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 4a0370f8e..3f42c25c1 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -3,6 +3,7 @@ import unittest.mock as umock from typing import List, Tuple +import helpers.hprint as hprint import helpers.hsystem as hsystem import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore @@ -121,60 +122,27 @@ def test_docker_build_single_arch(self) -> None: poetry_mode="update", ) actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - # Check build image commands. - build_cmds = actual_cmds[:2] - expected_build_cmds = [ - "cp -f devops/docker_build/dockerignore.dev .dockerignore", - "tar -czh . | DOCKER_BUILDKIT=0 time docker build " - " --no-cache " - " --build-arg AM_CONTAINER_VERSION=1.0.0 " - " --build-arg INSTALL_DIND=True " - " --build-arg POETRY_MODE=update " - " --build-arg CLEAN_UP_INSTALLATION=True " - f" --tag {test_base_image}:local-{self.user}-1.0.0 " - " --file devops/docker_build/dev.Dockerfile -", - ] - actual_build, expected_build = _convert_commands_to_strings( - build_cmds, expected_build_cmds - ) - self.assert_equal( - actual_build, - expected_build, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) - # Check poetry file commands. - poetry_cmds = actual_cmds[2:5] - expected_poetry_cmds = [ - "invoke docker_cmd " - "--stage local " - f"--version {test_version} " - f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", - "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", - "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", - ] - actual_poetry, expected_poetry = _convert_commands_to_strings( - poetry_cmds, expected_poetry_cmds - ) - self.assert_equal( - actual_poetry, - expected_poetry, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) - # Check list images command. - actual_list_cmd = actual_cmds[5] - expected_list_cmd = ( - f"docker image ls {test_base_image}:local-{self.user}-1.0.0" - ) - actual_list, expected_list = _convert_commands_to_strings( - actual_list_cmd, expected_list_cmd - ) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.dev .dockerignore + tar -czh . | DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --build-arg AM_CONTAINER_VERSION=1.0.0 --build-arg INSTALL_DIND=True --build-arg POETRY_MODE=update --build-arg CLEAN_UP_INSTALLATION=True \ + --tag test-registry.com/test-image:local-$USER_NAME-1.0.0 \ + --file devops/docker_build/dev.Dockerfile \ + - + invoke docker_cmd --stage local --version 1.0.0 --cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull + cp -f poetry.lock.out ./devops/docker_build/poetry.lock + cp -f pip_list.txt ./devops/docker_build/pip_list.txt + docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 + """ self.assert_equal( - actual_list, - expected_list, + actual_cmds, + exp, + purify_text=True, fuzzy_match=True, remove_lead_trail_empty_lines=True, dedent=True, From 1781088b5271f9d7937d2668e4ba0885f04cfd4f Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Thu, 1 May 2025 12:51:33 -0400 Subject: [PATCH 08/13] HelpersTask615: Propogate changes from previous commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 138 ++++++------------ 1 file changed, 43 insertions(+), 95 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 3f42c25c1..9568f7ff1 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,9 +1,7 @@ import logging -import re import unittest.mock as umock -from typing import List, Tuple +from typing import List -import helpers.hprint as hprint import helpers.hsystem as hsystem import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore @@ -12,51 +10,28 @@ _LOG = logging.getLogger(__name__) -def _normalize_command(cmd: str) -> str: - """ - Normalize a command string by removing line continuations and spaces around - special characters. - - :param cmd: command string to normalize - :return: normalized command string - """ - cmd = re.sub(r"\\\s*\n\s*", " ", cmd) - chars_to_normalize = ["=", ",", "|"] - escaped_chars = "".join(re.escape(ch) for ch in chars_to_normalize) - cmd = re.sub(rf"\s*([{escaped_chars}])\s*", r"\1", cmd) - return cmd - - -def _convert_commands_to_strings( - actual_cmds: List[str], expected_cmds: List[str] -) -> Tuple[str, str]: - """ - Convert a list of commands to strings and normalize them. - - :param actual_cmds: list of actual command strings - :param expected_cmds: list of expected command strings - :return: normalized actual commands string and normalized expected - commands string - """ - # Normalize each command in both lists. - actual_normalized = [_normalize_command(cmd) for cmd in actual_cmds] - expected_normalized = [_normalize_command(cmd) for cmd in expected_cmds] - # Convert to strings. - actual_str = "\n".join(actual_normalized) - expected_str = "\n".join(expected_normalized) - return actual_str, expected_str - - def _extract_commands_from_call(calls: List[umock._Call]) -> List[str]: """ Extract command strings from a list of mock call arguments. + Example: + calls = [ + ( + # args tuple: (context, command) + (mock_ctx, "docker build --no-cache image1"), + # kwargs dictionary + {"pty": True} + ) + ] + After extraction: + ["docker build --no-cache image1"] + :param calls: list of mock call objects containing (args, kwargs) :return: list of command strings """ # Each mock call is a (args, kwargs) tuple, extract the command string # from args[1] in each call. - call_list = [call[0][1] for call in calls] + call_list = [args_[1] for args_, kwargs_ in calls] return call_list @@ -171,63 +146,36 @@ def test_docker_build_multi_arch(self) -> None: multi_arch=test_multi_arch, ) actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - # Check buildx setup and build commands. - actual_build_cmds = actual_cmds[:3] - expected_build_cmds = [ - "cp -f devops/docker_build/dockerignore.dev .dockerignore", - "docker buildx create --name multiarch_builder --driver docker-container --bootstrap && docker buildx use multiarch_builder", - "tar -czh . | DOCKER_BUILDKIT=0 time docker buildx build " - " --no-cache " - " --push --platform linux/amd64,linux/arm64 " - " --build-arg AM_CONTAINER_VERSION=1.0.0 " - " --build-arg INSTALL_DIND=True " - " --build-arg POETRY_MODE=update " - " --build-arg CLEAN_UP_INSTALLATION=True " - f" --tag {test_base_image}:local-{self.user}-1.0.0 " - " --file devops/docker_build/dev.Dockerfile -", - ] - actual_build, expected_build = _convert_commands_to_strings( - actual_build_cmds, expected_build_cmds - ) - self.assert_equal( - actual_build, - expected_build, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) - # Check pull and poetry file commands. - actual_poetry_cmds = actual_cmds[3:7] - expected_poetry_cmds = [ - f"docker pull {test_base_image}:local-{self.user}-1.0.0", - "invoke docker_cmd " - "--stage local " - f"--version {test_version} " - f"--cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull", - "cp -f poetry.lock.out ./devops/docker_build/poetry.lock", - "cp -f pip_list.txt ./devops/docker_build/pip_list.txt", - ] - actual_poetry, expected_poetry = _convert_commands_to_strings( - actual_poetry_cmds, expected_poetry_cmds - ) - self.assert_equal( - actual_poetry, - expected_poetry, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) - # Check list images command. - actual_list_cmd = actual_cmds[7] - expected_list_cmd = ( - f"docker image ls {test_base_image}:local-{self.user}-1.0.0" - ) - actual_list, expected_list = _convert_commands_to_strings( - actual_list_cmd, expected_list_cmd - ) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.dev .dockerignore + docker buildx create \ + --name multiarch_builder \ + --driver docker-container \ + --bootstrap \ + && \ + docker buildx use multiarch_builder + tar -czh . | DOCKER_BUILDKIT=0 \ + time \ + docker buildx build \ + --no-cache \ + --push \ + --platform linux/amd64,linux/arm64 \ + --build-arg AM_CONTAINER_VERSION=1.0.0 --build-arg INSTALL_DIND=True --build-arg POETRY_MODE=update --build-arg CLEAN_UP_INSTALLATION=True \ + --tag test-registry.com/test-image:local-$USER_NAME-1.0.0 \ + --file devops/docker_build/dev.Dockerfile \ + - + docker pull test-registry.com/test-image:local-$USER_NAME-1.0.0 + invoke docker_cmd --stage local --version 1.0.0 --cmd 'cp -f /install/poetry.lock.out /install/pip_list.txt .' --skip-pull + cp -f poetry.lock.out ./devops/docker_build/poetry.lock + cp -f pip_list.txt ./devops/docker_build/pip_list.txt + docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 + """ self.assert_equal( - actual_list, - expected_list, + actual_cmds, + exp, + purify_text=True, fuzzy_match=True, remove_lead_trail_empty_lines=True, dedent=True, From aa08a680fe95110e982861b472a8df3a14aa9584 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Thu, 1 May 2025 13:02:06 -0400 Subject: [PATCH 09/13] HelpersTask615: Modify comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 9568f7ff1..5c0a74ab5 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -88,7 +88,7 @@ def test_docker_build_single_arch(self) -> None: mock_ctx = httestlib._build_mock_context_returning_ok() test_version = "1.0.0" test_base_image = "test-registry.com/test-image" - # Call tested function . + # Call tested function. hltadore.docker_build_local_image( mock_ctx, test_version, @@ -114,6 +114,7 @@ def test_docker_build_single_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ + # Check output. self.assert_equal( actual_cmds, exp, @@ -172,6 +173,7 @@ def test_docker_build_multi_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ + # Check output. self.assert_equal( actual_cmds, exp, From e545fcc641de7e747a7ea2e902a1748c8a6f11d4 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Fri, 2 May 2025 19:34:22 -0400 Subject: [PATCH 10/13] HelpersTask615: Added tests for building prod_image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 250 +++++++++++++++++- 1 file changed, 247 insertions(+), 3 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 5c0a74ab5..0077023da 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -2,7 +2,6 @@ import unittest.mock as umock from typing import List -import helpers.hsystem as hsystem import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore import helpers.test.test_lib_tasks as httestlib @@ -63,8 +62,6 @@ def setUp(self) -> None: "helpers.lib_tasks_docker.docker_login" ) self.mock_docker_login = self.docker_login_patcher.start() - # - self.user = hsystem.get_user_name() def tearDown(self) -> None: """ @@ -182,3 +179,250 @@ def test_docker_build_multi_arch(self) -> None: remove_lead_trail_empty_lines=True, dedent=True, ) + + +# ############################################################################# +# TestDockerBuildProdImage1 +# ############################################################################# + + +class TestDockerBuildProdImage1(hunitest.TestCase): + + def setUp(self) -> None: + """ + Set up test environment and initialize all necessary mocks. + """ + super().setUp() + # Mock system calls. + self.system_patcher = umock.patch("helpers.hsystem.system") + self.mock_system = self.system_patcher.start() + # Mock run. + self.run_patcher = umock.patch("helpers.lib_tasks_utils.run") + self.mock_run = self.run_patcher.start() + # Mock version validation. + self.version_patcher = umock.patch( + "helpers.lib_tasks_docker.dassert_is_subsequent_version" + ) + self.mock_version = self.version_patcher.start() + # Mock docker login. + self.docker_login_patcher = umock.patch( + "helpers.lib_tasks_docker.docker_login" + ) + self.mock_docker_login = self.docker_login_patcher.start() + # Mock environment variable. + self.env_patcher = umock.patch.dict( + "os.environ", {"CSFY_ECR_BASE_PATH": "dummy.ecr.path"} + ) + self.env_patcher.start() + + def tearDown(self) -> None: + """ + Clean up test environment by stopping all mocks after each test case. + """ + self.system_patcher.stop() + self.run_patcher.stop() + self.version_patcher.stop() + self.docker_login_patcher.stop() + self.env_patcher.stop() + super().tearDown() + + def test_docker_build_prod_image(self) -> None: + """ + Test building a prod Docker image with single architecture. + + This test verifies that the correct sequence of commands is + generated for building a prod Docker image. + """ + # Prepare inputs. + mock_ctx = httestlib._build_mock_context_returning_ok() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + # Call tested function. + hltadore.docker_build_prod_image( + mock_ctx, + test_version, + base_image=test_base_image, + cache=False, + ) + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-1.0.0 \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=dummy.ecr.path \ + . + docker tag test-registry.com/test-image:prod-1.0.0 test-registry.com/test-image:prod + docker image ls test-registry.com/test-image:prod + """ + # Check output. + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + + def test_docker_build_multi_arch_prod_image(self) -> None: + """ + Test building a prod Docker image with multiple architectures. + + This test verifies that the correct sequence of commands is + generated for building a multi-arch prod Docker image. + """ + # Prepare inputs. + mock_ctx = httestlib._build_mock_context_returning_ok() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_multi_arch = "linux/amd64,linux/arm64" + # Call tested function. + hltadore.docker_build_multi_arch_prod_image( + mock_ctx, + test_version, + base_image=test_base_image, + cache=False, + multi_arch=test_multi_arch, + ) + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + docker buildx create \ + --name multiarch_builder \ + --driver docker-container \ + --bootstrap \ + && \ + docker buildx use multiarch_builder + tar -czh . | DOCKER_BUILDKIT=0 \ + time \ + docker buildx build \ + --no-cache \ + --push \ + --platform linux/amd64,linux/arm64 \ + --build-arg VERSION=1.0.0 --build-arg ECR_BASE_PATH=dummy.ecr.path \ + --tag test-registry.com/test-image:prod-1.0.0 \ + --file devops/docker_build/prod.Dockerfile \ + - + docker pull test-registry.com/test-image:prod-1.0.0 + docker image ls test-registry.com/test-image:prod-1.0.0 + """ + # Check output. + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + + def test_docker_build_prod_image_with_candidate_tag(self) -> None: + """ + Test building a prod Docker image with candidate mode using tag. + + This test verifies that the correct sequence of commands is + generated for building a prod image with candidate mode using + tag. + """ + # Prepare inputs. + mock_ctx = httestlib._build_mock_context_returning_ok() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_tag = "test_tag" + # Call tested function. + hltadore.docker_build_prod_image( + mock_ctx, + test_version, + base_image=test_base_image, + cache=False, + candidate=True, + tag=test_tag, + ) + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-test_tag \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=dummy.ecr.path \ + . + docker image ls test-registry.com/test-image:prod-test_tag + """ + # Check output. + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + + def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: + """ + Test building a prod Docker image with candidate mode using user tag. + + This test verifies that the correct sequence of commands is + generated for building a prod image with candidate mode using + user tag and tag. + """ + # Prepare inputs. + mock_ctx = httestlib._build_mock_context_returning_ok() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_user_tag = "testuser" + test_tag = "test_tag" + # Call tested function. + hltadore.docker_build_prod_image( + mock_ctx, + test_version, + base_image=test_base_image, + cache=False, + candidate=True, + user_tag=test_user_tag, + tag=test_tag, + ) + actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) + actual_cmds = "\n".join(actual_cmds) + # The output is a list of strings, each representing a command. + exp = r""" + cp -f devops/docker_build/dockerignore.prod .dockerignore + DOCKER_BUILDKIT=0 \ + time \ + docker build \ + --no-cache \ + --tag test-registry.com/test-image:prod-testuser-test_tag \ + --file /app/devops/docker_build/prod.Dockerfile \ + --build-arg VERSION=1.0.0 \ + --build-arg ECR_BASE_PATH=dummy.ecr.path \ + . + docker image ls test-registry.com/test-image:prod-testuser-test_tag + """ + # Check output. + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) From fa46cc381d0a5f9b27ccc6efff9c1fca3049e6fc Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Mon, 5 May 2025 11:34:47 -0400 Subject: [PATCH 11/13] HelpersTask615: Add a helper test class to refactor common code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 241 +++++++----------- 1 file changed, 92 insertions(+), 149 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 0077023da..0609ee9eb 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,6 +1,8 @@ import logging import unittest.mock as umock -from typing import List +from typing import List, Tuple + +import invoke import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore @@ -34,12 +36,30 @@ def _extract_commands_from_call(calls: List[umock._Call]) -> List[str]: return call_list +def get_build_inputs() -> Tuple[invoke.MockContext, str, str, str]: + """ + Create common test inputs for docker build flow tests. + + :return: tuple containing mock context, test version, test base + image and test multi-arch + """ + mock_ctx = httestlib._build_mock_context_returning_ok() + test_version = "1.0.0" + test_base_image = "test-registry.com/test-image" + test_multi_arch = "linux/amd64,linux/arm64" + return mock_ctx, test_version, test_base_image, test_multi_arch + + # ############################################################################# -# TestDockerBuildLocalImage1 +# _DockerFlowTestHelper # ############################################################################# -class TestDockerBuildLocalImage1(hunitest.TestCase): +class _DockerFlowTestHelper(hunitest.TestCase): + """ + Helper test class to perform common setup, teardown logic and assertion + checks for Docker flow tests. + """ def setUp(self) -> None: """ @@ -62,17 +82,57 @@ def setUp(self) -> None: "helpers.lib_tasks_docker.docker_login" ) self.mock_docker_login = self.docker_login_patcher.start() + # Mock environment variable. + self.env_patcher = umock.patch.dict( + "os.environ", {"CSFY_ECR_BASE_PATH": "test.ecr.path"} + ) + self.env_patcher.start() def tearDown(self) -> None: """ Clean up test environment by stopping all mocks after each test case. """ - self.system_patcher.stop() - self.run_patcher.stop() - self.version_patcher.stop() - self.docker_login_patcher.stop() + patchers = [ + self.system_patcher, + self.run_patcher, + self.version_patcher, + self.docker_login_patcher, + self.env_patcher, + ] + for patcher in patchers: + patcher.stop() super().tearDown() + def _check_docker_command_output( + self, exp: str, call_args_list: List[umock._Call] + ) -> None: + """ + Check that the sequence of commands from mock calls matches the + expected string. + + :param exp: expected command string + :param call_args_list: list of mock call objects + """ + actual_cmds = _extract_commands_from_call(call_args_list) + actual_cmds = "\n".join(actual_cmds) + self.assert_equal( + actual_cmds, + exp, + purify_text=True, + purify_expected_text=True, + fuzzy_match=True, + remove_lead_trail_empty_lines=True, + dedent=True, + ) + + +# ############################################################################# +# Test_docker_build_local_image1 +# ############################################################################# + + +class Test_docker_build_local_image1(_DockerFlowTestHelper): + def test_docker_build_single_arch(self) -> None: """ Test building a local Docker image with single architecture. @@ -82,9 +142,7 @@ def test_docker_build_single_arch(self) -> None: architecture. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" + mock_ctx, test_version, test_base_image, _ = get_build_inputs() # Call tested function. hltadore.docker_build_local_image( mock_ctx, @@ -93,8 +151,6 @@ def test_docker_build_single_arch(self) -> None: base_image=test_base_image, poetry_mode="update", ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.dev .dockerignore @@ -111,15 +167,7 @@ def test_docker_build_single_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) def test_docker_build_multi_arch(self) -> None: """ @@ -130,10 +178,9 @@ def test_docker_build_multi_arch(self) -> None: architectures. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" - test_multi_arch = "linux/amd64,linux/arm64" + mock_ctx, test_version, test_base_image, test_multi_arch = ( + get_build_inputs() + ) # Call tested function. hltadore.docker_build_local_image( mock_ctx, @@ -143,9 +190,6 @@ def test_docker_build_multi_arch(self) -> None: poetry_mode="update", multi_arch=test_multi_arch, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.dev .dockerignore docker buildx create \ @@ -170,61 +214,15 @@ def test_docker_build_multi_arch(self) -> None: cp -f pip_list.txt ./devops/docker_build/pip_list.txt docker image ls test-registry.com/test-image:local-$USER_NAME-1.0.0 """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) # ############################################################################# -# TestDockerBuildProdImage1 +# Test_docker_build_prod_image1 # ############################################################################# -class TestDockerBuildProdImage1(hunitest.TestCase): - - def setUp(self) -> None: - """ - Set up test environment and initialize all necessary mocks. - """ - super().setUp() - # Mock system calls. - self.system_patcher = umock.patch("helpers.hsystem.system") - self.mock_system = self.system_patcher.start() - # Mock run. - self.run_patcher = umock.patch("helpers.lib_tasks_utils.run") - self.mock_run = self.run_patcher.start() - # Mock version validation. - self.version_patcher = umock.patch( - "helpers.lib_tasks_docker.dassert_is_subsequent_version" - ) - self.mock_version = self.version_patcher.start() - # Mock docker login. - self.docker_login_patcher = umock.patch( - "helpers.lib_tasks_docker.docker_login" - ) - self.mock_docker_login = self.docker_login_patcher.start() - # Mock environment variable. - self.env_patcher = umock.patch.dict( - "os.environ", {"CSFY_ECR_BASE_PATH": "dummy.ecr.path"} - ) - self.env_patcher.start() - - def tearDown(self) -> None: - """ - Clean up test environment by stopping all mocks after each test case. - """ - self.system_patcher.stop() - self.run_patcher.stop() - self.version_patcher.stop() - self.docker_login_patcher.stop() - self.env_patcher.stop() - super().tearDown() +class Test_docker_build_prod_image1(_DockerFlowTestHelper): def test_docker_build_prod_image(self) -> None: """ @@ -234,9 +232,7 @@ def test_docker_build_prod_image(self) -> None: generated for building a prod Docker image. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" + mock_ctx, test_version, test_base_image, _ = get_build_inputs() # Call tested function. hltadore.docker_build_prod_image( mock_ctx, @@ -244,9 +240,6 @@ def test_docker_build_prod_image(self) -> None: base_image=test_base_image, cache=False, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.prod .dockerignore DOCKER_BUILDKIT=0 \ @@ -256,21 +249,12 @@ def test_docker_build_prod_image(self) -> None: --tag test-registry.com/test-image:prod-1.0.0 \ --file /app/devops/docker_build/prod.Dockerfile \ --build-arg VERSION=1.0.0 \ - --build-arg ECR_BASE_PATH=dummy.ecr.path \ + --build-arg ECR_BASE_PATH=test.ecr.path \ . docker tag test-registry.com/test-image:prod-1.0.0 test-registry.com/test-image:prod docker image ls test-registry.com/test-image:prod """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - purify_expected_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) def test_docker_build_multi_arch_prod_image(self) -> None: """ @@ -280,10 +264,9 @@ def test_docker_build_multi_arch_prod_image(self) -> None: generated for building a multi-arch prod Docker image. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" - test_multi_arch = "linux/amd64,linux/arm64" + mock_ctx, test_version, test_base_image, test_multi_arch = ( + get_build_inputs() + ) # Call tested function. hltadore.docker_build_multi_arch_prod_image( mock_ctx, @@ -292,9 +275,6 @@ def test_docker_build_multi_arch_prod_image(self) -> None: cache=False, multi_arch=test_multi_arch, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.prod .dockerignore docker buildx create \ @@ -309,23 +289,14 @@ def test_docker_build_multi_arch_prod_image(self) -> None: --no-cache \ --push \ --platform linux/amd64,linux/arm64 \ - --build-arg VERSION=1.0.0 --build-arg ECR_BASE_PATH=dummy.ecr.path \ + --build-arg VERSION=1.0.0 --build-arg ECR_BASE_PATH=test.ecr.path \ --tag test-registry.com/test-image:prod-1.0.0 \ --file devops/docker_build/prod.Dockerfile \ - docker pull test-registry.com/test-image:prod-1.0.0 docker image ls test-registry.com/test-image:prod-1.0.0 """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - purify_expected_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) def test_docker_build_prod_image_with_candidate_tag(self) -> None: """ @@ -336,9 +307,7 @@ def test_docker_build_prod_image_with_candidate_tag(self) -> None: tag. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" + mock_ctx, test_version, test_base_image, _ = get_build_inputs() test_tag = "test_tag" # Call tested function. hltadore.docker_build_prod_image( @@ -349,9 +318,6 @@ def test_docker_build_prod_image_with_candidate_tag(self) -> None: candidate=True, tag=test_tag, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.prod .dockerignore DOCKER_BUILDKIT=0 \ @@ -361,20 +327,11 @@ def test_docker_build_prod_image_with_candidate_tag(self) -> None: --tag test-registry.com/test-image:prod-test_tag \ --file /app/devops/docker_build/prod.Dockerfile \ --build-arg VERSION=1.0.0 \ - --build-arg ECR_BASE_PATH=dummy.ecr.path \ + --build-arg ECR_BASE_PATH=test.ecr.path \ . docker image ls test-registry.com/test-image:prod-test_tag """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - purify_expected_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: """ @@ -385,10 +342,8 @@ def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: user tag and tag. """ # Prepare inputs. - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" - test_user_tag = "testuser" + mock_ctx, test_version, test_base_image, _ = get_build_inputs() + test_user_tag = "test_user" test_tag = "test_tag" # Call tested function. hltadore.docker_build_prod_image( @@ -400,29 +355,17 @@ def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: user_tag=test_user_tag, tag=test_tag, ) - actual_cmds = _extract_commands_from_call(self.mock_run.call_args_list) - actual_cmds = "\n".join(actual_cmds) - # The output is a list of strings, each representing a command. exp = r""" cp -f devops/docker_build/dockerignore.prod .dockerignore DOCKER_BUILDKIT=0 \ time \ docker build \ --no-cache \ - --tag test-registry.com/test-image:prod-testuser-test_tag \ + --tag test-registry.com/test-image:prod-test_user-test_tag \ --file /app/devops/docker_build/prod.Dockerfile \ --build-arg VERSION=1.0.0 \ - --build-arg ECR_BASE_PATH=dummy.ecr.path \ + --build-arg ECR_BASE_PATH=test.ecr.path \ . - docker image ls test-registry.com/test-image:prod-testuser-test_tag + docker image ls test-registry.com/test-image:prod-test_user-test_tag """ - # Check output. - self.assert_equal( - actual_cmds, - exp, - purify_text=True, - purify_expected_text=True, - fuzzy_match=True, - remove_lead_trail_empty_lines=True, - dedent=True, - ) + self._check_docker_command_output(exp, self.mock_run.call_args_list) From 816c2da66a0da553a043fd6f04fc4e928430581f Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Mon, 5 May 2025 12:59:46 -0400 Subject: [PATCH 12/13] HelpersTask615: Move inputs into setUp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 94 +++++++------------ 1 file changed, 34 insertions(+), 60 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index 0609ee9eb..d2bac09ea 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,8 +1,6 @@ import logging import unittest.mock as umock -from typing import List, Tuple - -import invoke +from typing import List import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore @@ -36,20 +34,6 @@ def _extract_commands_from_call(calls: List[umock._Call]) -> List[str]: return call_list -def get_build_inputs() -> Tuple[invoke.MockContext, str, str, str]: - """ - Create common test inputs for docker build flow tests. - - :return: tuple containing mock context, test version, test base - image and test multi-arch - """ - mock_ctx = httestlib._build_mock_context_returning_ok() - test_version = "1.0.0" - test_base_image = "test-registry.com/test-image" - test_multi_arch = "linux/amd64,linux/arm64" - return mock_ctx, test_version, test_base_image, test_multi_arch - - # ############################################################################# # _DockerFlowTestHelper # ############################################################################# @@ -87,19 +71,25 @@ def setUp(self) -> None: "os.environ", {"CSFY_ECR_BASE_PATH": "test.ecr.path"} ) self.env_patcher.start() - - def tearDown(self) -> None: - """ - Clean up test environment by stopping all mocks after each test case. - """ - patchers = [ + # + self.patchers = [ self.system_patcher, self.run_patcher, self.version_patcher, self.docker_login_patcher, self.env_patcher, ] - for patcher in patchers: + # Test inputs. + self.mock_ctx = httestlib._build_mock_context_returning_ok() + self.test_version = "1.0.0" + self.test_base_image = "test-registry.com/test-image" + self.test_multi_arch = "linux/amd64,linux/arm64" + + def tearDown(self) -> None: + """ + Clean up test environment by stopping all mocks after each test case. + """ + for patcher in self.patchers: patcher.stop() super().tearDown() @@ -141,14 +131,12 @@ def test_docker_build_single_arch(self) -> None: generated for building a local Docker image with single architecture. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, _ = get_build_inputs() # Call tested function. hltadore.docker_build_local_image( - mock_ctx, - test_version, + self.mock_ctx, + self.test_version, cache=False, - base_image=test_base_image, + base_image=self.test_base_image, poetry_mode="update", ) # The output is a list of strings, each representing a command. @@ -177,18 +165,14 @@ def test_docker_build_multi_arch(self) -> None: generated for building a local Docker image with multiple architectures. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, test_multi_arch = ( - get_build_inputs() - ) # Call tested function. hltadore.docker_build_local_image( - mock_ctx, - test_version, + self.mock_ctx, + self.test_version, cache=False, - base_image=test_base_image, + base_image=self.test_base_image, poetry_mode="update", - multi_arch=test_multi_arch, + multi_arch=self.test_multi_arch, ) exp = r""" cp -f devops/docker_build/dockerignore.dev .dockerignore @@ -231,13 +215,11 @@ def test_docker_build_prod_image(self) -> None: This test verifies that the correct sequence of commands is generated for building a prod Docker image. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, _ = get_build_inputs() # Call tested function. hltadore.docker_build_prod_image( - mock_ctx, - test_version, - base_image=test_base_image, + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, cache=False, ) exp = r""" @@ -263,17 +245,13 @@ def test_docker_build_multi_arch_prod_image(self) -> None: This test verifies that the correct sequence of commands is generated for building a multi-arch prod Docker image. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, test_multi_arch = ( - get_build_inputs() - ) # Call tested function. hltadore.docker_build_multi_arch_prod_image( - mock_ctx, - test_version, - base_image=test_base_image, + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, cache=False, - multi_arch=test_multi_arch, + multi_arch=self.test_multi_arch, ) exp = r""" cp -f devops/docker_build/dockerignore.prod .dockerignore @@ -306,14 +284,12 @@ def test_docker_build_prod_image_with_candidate_tag(self) -> None: generated for building a prod image with candidate mode using tag. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, _ = get_build_inputs() test_tag = "test_tag" # Call tested function. hltadore.docker_build_prod_image( - mock_ctx, - test_version, - base_image=test_base_image, + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, cache=False, candidate=True, tag=test_tag, @@ -341,15 +317,13 @@ def test_docker_build_prod_image_with_candidate_user_tag(self) -> None: generated for building a prod image with candidate mode using user tag and tag. """ - # Prepare inputs. - mock_ctx, test_version, test_base_image, _ = get_build_inputs() test_user_tag = "test_user" test_tag = "test_tag" # Call tested function. hltadore.docker_build_prod_image( - mock_ctx, - test_version, - base_image=test_base_image, + self.mock_ctx, + self.test_version, + base_image=self.test_base_image, cache=False, candidate=True, user_tag=test_user_tag, From 6fdf6bfc9f095974f33e32bb6198719762e61561 Mon Sep 17 00:00:00 2001 From: Sandeep Thalapanane Date: Mon, 5 May 2025 14:35:02 -0400 Subject: [PATCH 13/13] HelpersTask615: Change setUp and tearDown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-commit checks: - 'check_master' passed - 'check_author' passed - 'check_file_size' passed - 'check_python_compile' passed - 'check_gitleaks' passed All checks passed ✅ --- helpers/test/test_lib_tasks_docker_release.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/helpers/test/test_lib_tasks_docker_release.py b/helpers/test/test_lib_tasks_docker_release.py index d2bac09ea..39f552579 100644 --- a/helpers/test/test_lib_tasks_docker_release.py +++ b/helpers/test/test_lib_tasks_docker_release.py @@ -1,6 +1,8 @@ import logging import unittest.mock as umock -from typing import List +from typing import Generator, List + +import pytest import helpers.hunit_test as hunitest import helpers.lib_tasks_docker_release as hltadore @@ -45,11 +47,13 @@ class _DockerFlowTestHelper(hunitest.TestCase): checks for Docker flow tests. """ - def setUp(self) -> None: - """ - Set up test environment and initialize all necessary mocks. - """ - super().setUp() + @pytest.fixture(autouse=True) + def setup_teardown_test(self) -> Generator: + self.set_up_test() + yield + self.tear_down_test() + + def set_up_test(self) -> None: # Mock system calls. self.system_patcher = umock.patch("helpers.hsystem.system") self.mock_system = self.system_patcher.start() @@ -85,13 +89,12 @@ def setUp(self) -> None: self.test_base_image = "test-registry.com/test-image" self.test_multi_arch = "linux/amd64,linux/arm64" - def tearDown(self) -> None: + def tear_down_test(self) -> None: """ Clean up test environment by stopping all mocks after each test case. """ for patcher in self.patchers: patcher.stop() - super().tearDown() def _check_docker_command_output( self, exp: str, call_args_list: List[umock._Call] @@ -123,7 +126,7 @@ def _check_docker_command_output( class Test_docker_build_local_image1(_DockerFlowTestHelper): - def test_docker_build_single_arch(self) -> None: + def test_docker_build_local_image_single_arch(self) -> None: """ Test building a local Docker image with single architecture. @@ -157,7 +160,7 @@ def test_docker_build_single_arch(self) -> None: """ self._check_docker_command_output(exp, self.mock_run.call_args_list) - def test_docker_build_multi_arch(self) -> None: + def test_docker_build_local_image_multi_arch(self) -> None: """ Test building a local Docker image with multiple architectures.