Skip to content

Commit

Permalink
Take a Swing at Unit Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisoverzero committed Apr 11, 2024
1 parent 75136ff commit e38215a
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 7 deletions.
8 changes: 4 additions & 4 deletions samcli/lib/build/app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,14 @@ def _stream_lambda_image_build_logs(self, build_logs: List[Dict[str, str]], func
raise DockerBuildFailed(msg=f"{function_name} failed to build: {str(ex)}") from ex

def _load_lambda_image(self, image_archive_path: str) -> str:
with open(image_archive_path, mode="rb") as image_archive:
try:
try:
with open(image_archive_path, mode="rb") as image_archive:
[image, *rest] = self._docker_client.images.load(image_archive)
if len(rest) != 0:
raise DockerBuildFailed("Archive must represent a single image")
return f"{image.id}"
except docker.errors.APIError as ex:
raise DockerBuildFailed(msg=str(ex)) from ex
except (docker.errors.APIError, OSError) as ex:
raise DockerBuildFailed(msg=str(ex)) from ex

def _build_layer(
self,
Expand Down
68 changes: 66 additions & 2 deletions tests/unit/lib/build_module/test_app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import json

from unittest import TestCase
from unittest.mock import Mock, MagicMock, call, patch, ANY
from unittest.mock import Mock, MagicMock, call, mock_open, patch, ANY
from pathlib import Path, WindowsPath

from parameterized import parameterized
Expand Down Expand Up @@ -1734,10 +1734,61 @@ def test_can_raise_build_error(self):
self.builder._build_lambda_image("Name", {}, X86_64)


class TestApplicationBuilder_load_lambda_image_function(TestCase):
def setUp(self):
self.docker_client_mock = Mock()
self.builder = ApplicationBuilder(
Mock(),
"/build/dir",
"/base/dir",
"/cached/dir",
stream_writer=Mock(),
docker_client=self.docker_client_mock,
)

@patch("builtins.open", new_callable=mock_open)
def test_loads_image_archive(self, mock_open):
id = "sha256:1a2b3c4d5e6f"

self.docker_client_mock.images.load.return_value = [Mock(id=id)]

image = self.builder._load_lambda_image("./path/to/archive.tar.gz")
self.assertEqual(id, image)

@patch("builtins.open", new_callable=mock_open)
def test_must_represent_a_single_image(self, mock_open):
self.docker_client_mock.images.load.return_value = [
Mock(id="sha256:1a2b3c4d5e6f"),
Mock(id="sha256:1f2e3d4c5b6a"),
]

with self.assertRaises(DockerBuildFailed) as ex:
self.builder._load_lambda_image("./path/to/archive.tar.gz")
self.assertIn("single", str(ex.exception))

@patch("builtins.open", side_effect=OSError)
def test_image_archive_does_not_exist(self, mock_open):
with self.assertRaises(DockerBuildFailed):
self.builder._load_lambda_image("./path/to/nowhere.tar.gz")

@patch("builtins.open", new_callable=mock_open)
def test_docker_api_error(self, mock_open):
self.docker_client_mock.images.load.side_effect = docker.errors.APIError("failed to dial")

with self.assertRaises(DockerBuildFailed):
self.builder._load_lambda_image("./path/to/archive.tar.gz")


class TestApplicationBuilder_build_function(TestCase):
def setUp(self):
self.docker_client_mock = Mock()
self.builder = ApplicationBuilder(
Mock(), "/build/dir", "/base/dir", "cachedir", stream_writer=StreamWriter(sys.stderr)
Mock(),
"/build/dir",
"/base/dir",
"cachedir",
stream_writer=StreamWriter(sys.stderr),
docker_client=self.docker_client_mock,
)

@patch("samcli.lib.build.app_builder.get_workflow_config")
Expand Down Expand Up @@ -2547,6 +2598,19 @@ def test_must_build_in_container_with_custom_default_build_image(self, osutils_m
specified_workflow=None,
)

@parameterized.expand([X86_64, ARM64])
@patch.object(Path, "is_file", return_value=True)
@patch("builtins.open", new_callable=mock_open)
def test_loads_if_path_exists(self, mock_open, mock_is_file, architecture):
id = "sha256:1a2b3c4d5e6f"
function_name = "function_name"
imageuri = str(Path("./path/to/archive.tar.gz"))

self.docker_client_mock.images.load.return_value = [Mock(id=id)]

image = self.builder._build_function(function_name, None, imageuri, IMAGE, None, architecture, None, None)
self.assertEqual(id, image)


class TestApplicationBuilder_build_function_in_process(TestCase):
def setUp(self):
Expand Down
96 changes: 95 additions & 1 deletion tests/unit/lib/package/test_ecr_uploader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest import TestCase
from unittest.mock import MagicMock, patch, call
from unittest.mock import MagicMock, Mock, call, mock_open, patch

from pathlib import Path
from botocore.exceptions import ClientError
from docker.errors import APIError, BuildError
from parameterized import parameterized
Expand Down Expand Up @@ -194,6 +195,99 @@ def test_upload_failure_while_streaming(self):
with self.assertRaises(DockerPushFailedError):
ecr_uploader.upload(image, resource_name="HelloWorldFunction")

@patch.object(Path, "is_file", return_value=True)
@patch("builtins.open", new_callable=mock_open)
def test_upload_from_image_archive(self, mock_open, mock_is_file):
resource_name = "HelloWorldFunction"
digest = "1a2b3c4d5e6f"
id = f"sha256:{digest}"
image = "./path/to/archive.tar.gz"

self.docker_client.images.load.return_value = [Mock(id=id)]
self.docker_client.api.push.return_value.__iter__.return_value = iter(
[
{"status": "Pushing to xyz"},
{"id": "1", "status": "Preparing", "progress": ""},
{"id": "2", "status": "Preparing", "progress": ""},
{"id": "3", "status": "Preparing", "progress": ""},
{"id": "1", "status": "Pushing", "progress": "[====> ]"},
{"id": "3", "status": "Pushing", "progress": "[====> ]"},
{"id": "2", "status": "Pushing", "progress": "[====> ]"},
{"id": "3", "status": "Pushed", "progress": "[========>]"},
{"id": "1", "status": "Pushed", "progress": "[========>]"},
{"id": "2", "status": "Pushed", "progress": "[========>]"},
{"status": f"image {resource_name} pushed digest: {digest}"},
{},
]
)

ecr_uploader = ECRUploader(
docker_client=self.docker_client,
ecr_client=self.ecr_client,
ecr_repo=self.ecr_repo,
ecr_repo_multi=self.ecr_repo_multi,
tag=self.tag,
)
ecr_uploader.login = MagicMock()
tag = ecr_uploader.upload(image, resource_name=resource_name)
self.assertEqual(f"{self.ecr_repo}:{resource_name}-{digest}-{self.tag}", tag)

@patch.object(Path, "is_file", return_value=False)
def test_upload_from_digest(self, mock_is_file):
resource_name = "HelloWorldFunction"
digest = "1a2b3c4d5e6f"
id = f"sha256:{digest}"
image = id

self.docker_client.images.get.return_value = Mock(id=id)
self.docker_client.api.push.return_value.__iter__.return_value = iter(
[
{"status": "Pushing to xyz"},
{"id": "1", "status": "Preparing", "progress": ""},
{"id": "2", "status": "Preparing", "progress": ""},
{"id": "3", "status": "Preparing", "progress": ""},
{"id": "1", "status": "Pushing", "progress": "[====> ]"},
{"id": "3", "status": "Pushing", "progress": "[====> ]"},
{"id": "2", "status": "Pushing", "progress": "[====> ]"},
{"id": "3", "status": "Pushed", "progress": "[========>]"},
{"id": "1", "status": "Pushed", "progress": "[========>]"},
{"id": "2", "status": "Pushed", "progress": "[========>]"},
{"status": f"image {resource_name} pushed digest: {digest}"},
{},
]
)

ecr_uploader = ECRUploader(
docker_client=self.docker_client,
ecr_client=self.ecr_client,
ecr_repo=self.ecr_repo,
ecr_repo_multi=self.ecr_repo_multi,
tag=self.tag,
)
ecr_uploader.login = MagicMock()
tag = ecr_uploader.upload(image, resource_name=resource_name)
self.assertEqual(f"{self.ecr_repo}:{resource_name}-{digest}-{self.tag}", tag)

@patch.object(Path, "is_file", return_value=True)
@patch("builtins.open", new_callable=mock_open)
def test_upload_failure_if_archive_represents_multiple_images(self, mock_open, mock_is_file):
resource_name = "HelloWorldFunction"
image = "./path/to/archive.tar.gz"

self.docker_client.images.load.return_value = [Mock(), Mock()]

ecr_uploader = ECRUploader(
docker_client=self.docker_client,
ecr_client=self.ecr_client,
ecr_repo=self.ecr_repo,
ecr_repo_multi=self.ecr_repo_multi,
tag=self.tag,
)
ecr_uploader.login = MagicMock()

with self.assertRaises(DockerPushFailedError):
ecr_uploader.upload(image, resource_name=resource_name)

@patch("samcli.lib.package.ecr_uploader.click.echo")
def test_delete_artifact_successful(self, patched_click_echo):
ecr_uploader = ECRUploader(
Expand Down

0 comments on commit e38215a

Please sign in to comment.