Skip to content

Feat: added integration tests, container tag check and layer support #2780

Merged
qingchm merged 24 commits intoaws:feat/public-ecrfrom
qingchm:feat/public-ecr
Apr 5, 2021
Merged

Feat: added integration tests, container tag check and layer support #2780
qingchm merged 24 commits intoaws:feat/public-ecrfrom
qingchm:feat/public-ecr

Conversation

@qingchm
Copy link
Contributor

@qingchm qingchm commented Mar 30, 2021

Which issue(s) does this change fix?

N.A.

Why is this change necessary?

To add integration tests for new option --build-image

How does it address the issue?

N.A.

What side effects does this change have?

N.A.

Checklist

  • Add input/output type hints to new functions/methods
  • Write design document (Do I need to write a design document?)
  • Write unit tests
  • Write/update functional tests
  • Write/update integration tests
  • make pr passes
  • make update-reproducible-reqs if dependencies were changed
  • Write documentation

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor

@jfuss jfuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic seems generally good and I like how you adding the Container option class!

Can we make sure we have unit tests for any code we added? It looks like we don't have tests for the Container class or the additional logic you added into the app_builder. Pytest should give you lines/files that are missing coverage and you can use that as a starting place to have the lines/files you added are covered.

Comment on lines 444 to 450
image = None
if self._build_images is not None:
if layer_name in self._build_images:
image = self._build_images[layer_name]
elif None in self._build_images:
image = self._build_images[None]
self._build_function_on_container(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am slightly confused on this. Which I think is mainly due to the nesting.

A couple questions:

  1. Can self._build_images be None? If so, is there a way we can safely default this to an empty dict instead?
  2. Why are we checking if the layer_name is in self._build_images?
  3. I am not familiar with the build_images property and how it gets set. Could you describe the contents?

Something to think about for the future:
When you are writing deeply nested code, consider ways you can simplify it. There are usually ways to write the same logic with less control flows or splitting the nesting into simpler private functions. So in this case, if we could default self._build_images to an empty dict in __init__ then we safe ourselves the need to do None checks (we do this throughout the code base for this reason).

When acting on dictionaries in python, try to avoid using [] to access elements and instead use .get() method. The .get() method does the checks you want for you. So you can simplify both inner if statements by doing image = self._build_images.get(layer_name). By default, this will return None if the element (layer_name) is not in the dict.

I think I actually answered my above questions while writing the last part but will leave the in the comment to help explain my thought process/questions I would ask.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes it is defaulted to be none. Type hinting doesn't allow any parameter defaults to be {}, so no.
  2. self._build_images has function names and layer names as keys and image uri's as value.
  3. Details in feat(Public ECR): Added --build-image Options #2725, basically we take the command line argument and make a dict that stores the URI's and their related logicalId's

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on using get(), this is following the previous pr's pattern, will change both to get()

Copy link
Contributor Author

@qingchm qingchm Mar 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we can set self._build_images to buildimages or {} during assignment, for sure this would be safer

@parameterized.expand(
[
("use_container", None),
("use_container", "amazon/aws-sam-cli-build-image-nodejs10.x"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the languages our integ tests use the same as others (python I think)? This helps us in keeping things up to date across all our tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can change the test case to python then


self.verify_docker_container_cleanedup("nodejs10.x")

def _verify_right_image_pulled(self, build_image, process_stderr):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we have helper functions that do this already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is checking the name of the image, I don't think we have anything similar

self.verify_docker_container_cleanedup("nodejs10.x")

def _verify_right_image_pulled(self, build_image, process_stderr):
image_name = build_image if build_image is not None else "public.ecr.aws/sam/build-nodejs10.x"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you really want to stay with node (which I prefer we keep all our integ tests the same language unless really testing something lang specific), we should be using the latest node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already added the support to default the tags to be latest if we don't input the tag, so this is exactly the same thing. But for the testing we can test if latest tag is included

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not talking about the image tag here. You are using Nodejs10.x, which is the oldest of the 2 Node versions Lambda supports. My comment is to not use the oldest and instead us the latest version of Node (or any language we us in our integ tests).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, this can save us some work (or delay the work) when we deprecate node 10

process = Popen(cmdlist, cwd=self.working_dir, stdout=PIPE, stderr=PIPE)
try:
stdout, stderr = process.communicate(timeout=TIMEOUT)
LOG.info(f"Stdout: {stdout.decode('utf-8')}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this info always displayed in our test output?

Same goes for other LOG. statements, mainly those who will output logs of data (stderr and stdout).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this same logging behaviour for all of our integration tests, when we run the command @jfuss

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other build tests we do not log stdout and stderr. This comment is about removing the large outputs in our tests logs. Leaving what commands and other small information is fine, logging 30+ lines seems like overkill. If you think this info is important, then we should only output it on test failure instead of every single run.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we do but it is done through the run_command:

def run_command(command_list, cwd=None, env=None, timeout=TIMEOUT) -> CommandResult:
Why are we not using this here?

Copy link
Contributor Author

@qingchm qingchm Apr 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need to do extra work that run_command does not provide, which is checking the content in the stderr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do support checking stderr using run_command

command_result = run_command(cmdlist, cwd=self.working_dir)
# make sure functions are deduplicated properly, in stderr they will show up in the same line.
self.assertRegex(command_result.stderr.decode("utf-8"), r"Building .+'Function2',.+LocalNestedStack/Function2")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try something like this, trying to use the nametuple returned

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I had several differences between this function and run command, but seems like this run_command will return what I need

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use run_command()

LOG.debug(Path(config_path).read_text())
runner = CliRunner()
result = runner.invoke(cli, [])
result = runner.invoke(cli, ["--use-container"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding --use-container to all of these? It's unclear why we need to update this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because otherwise as the CliRunner doesn't see this tag, it fails the invocation and showcases the ContainerTag check error

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something but my initial feeling is that seems wrong. Wouldn't this mean we are requiring --use-container now? The command shouldn't fail if we don't provided --use-container to the cli.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should now, we are going to fail any use of container related tags if we don't provide --use-container to the cli instead of ignoring them now

import click


class Container(click.Option):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we name this something more specific like ContainerOptions? We have other Container classes and might be easier to read/understand the difference at a glance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I will change the name

Copy link
Contributor

@aahung aahung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it handle if I want to specify a build image for a function in nested stack like --build-image=ChildStack.FunctionA. It will be great if we have an integ test on that

@qingchm
Copy link
Contributor Author

qingchm commented Mar 31, 2021

The logic seems generally good and I like how you adding the Container option class!

Can we make sure we have unit tests for any code we added? It looks like we don't have tests for the Container class or the additional logic you added into the app_builder. Pytest should give you lines/files that are missing coverage and you can use that as a starting place to have the lines/files you added are covered.

Some unit tests are already included in the previous pr. Adding additional unit tests for layer builds. As for Container class, I don't think we have tests on click_mutex class either. Are we looking to add tests for click related code?

@qingchm qingchm requested a review from jfuss April 1, 2021 02:10
process = Popen(cmdlist, cwd=self.working_dir, stdout=PIPE, stderr=PIPE)
try:
stdout, stderr = process.communicate(timeout=TIMEOUT)
LOG.info(f"Stdout: {stdout.decode('utf-8')}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we do but it is done through the run_command:

def run_command(command_list, cwd=None, env=None, timeout=TIMEOUT) -> CommandResult:
Why are we not using this here?

import click


class ContainerOptions(click.Option):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have unit tests for this class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't, I don't see any tests related to https://github.com/aws/aws-sam-cli/blob/develop/samcli/commands/local/cli_common/click_mutex.py either, let me know your thoughts regarding testing on click.Option classes! One of the ways that I can think of testing it would be to create a mock CliRunner and verify that command fails when we provide container related tags without -u

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because we do not have tests for another class, doesn't mean we should not be adding tests for new code. We always want to improve the code base through adding tests. With that logic one could say, we shouldn't be adding any tests because we have a class that doesn't have tests at all.

Please add unit tests for code we add, so we have some basic checks in place on that expectation of that code.

We shouldn't need to mock a CliRunner here or verify commands (kind of out of scope of a unit tests in my opinion). What we want to verify is that the logic you added is correct. So you can mock out methods, input, etc to help in validating what you expect to happen does happen. At the very least we should have tests to cover:

if "use_container" not in opts and self.name in opts:
            opt_name = self.name.replace("_", "-")
            msg = f"Missing required parameter, need the --use-container flag in order to use --{opt_name} flag."
            raise click.UsageError(msg)

An alternative way to do this is by creating a sub method and testing that, if mocking the rest of things becomes a pain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding two branches of tests

Copy link
Contributor

@aahung aahung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, left some small comments/questions

process = Popen(cmdlist, cwd=self.working_dir, stdout=PIPE, stderr=PIPE)
try:
stdout, stderr = process.communicate(timeout=TIMEOUT)
LOG.info(f"Stdout: {stdout.decode('utf-8')}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do support checking stderr using run_command

command_result = run_command(cmdlist, cwd=self.working_dir)
# make sure functions are deduplicated properly, in stderr they will show up in the same line.
self.assertRegex(command_result.stderr.decode("utf-8"), r"Building .+'Function2',.+LocalNestedStack/Function2")

@qingchm qingchm requested a review from jfuss April 1, 2021 22:37


@patch("samcli.commands.build.click_container.ContainerOptions")
class TestContainerOptionsSucceeds(TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Since this is in its own file, lets match that with our unit tests as well. This is will make it consistent with all our other tests in the repo and make it easier to know where to update tests in the future when things change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

Copy link
Contributor

@jfuss jfuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for handling all the feedback through this PR.

@qingchm qingchm merged commit 92b1453 into aws:feat/public-ecr Apr 5, 2021
qingchm added a commit that referenced this pull request Apr 5, 2021
* feat(Public ECR): Changed Build Image Registry to Public ECR (#2708)

* Changed Build Image Registry to Public ECR

* Added  _IMAGE_URI_PREFIX

* Fixed Unit Tests

* Updated Image Format

* feat(Public ECR): Updated Test Image Name and Added Latest Tag Check (#2720)

* Added Tests for Latest Tag

* Added use_container Check for Verify Latest Tag Tests

* Fixed DotNet and Go Build Tests

* Added Missing use_container Check for test_build_function_and_layer

* feat(Public ECR): Changed Build Image Registry to Public ECR (#2708)

* Changed Build Image Registry to Public ECR

* Added  _IMAGE_URI_PREFIX

* Fixed Unit Tests

* Updated Image Format

* feat(Public ECR): Updated Test Image Name and Added Latest Tag Check (#2720)

* Added Tests for Latest Tag

* Added use_container Check for Verify Latest Tag Tests

* Fixed DotNet and Go Build Tests

* Added Missing use_container Check for test_build_function_and_layer

* feat(Public ECR): Added --build-image Options (#2725)

* Added Latest Tag Check

* Added --build-image Option

* Added Unit Tests

* Added Help Text and Fixed PyLint

* Fixed Path Validation for Unit Tests

* Updated Type Hintings

* Added _parse_key_value_pair

* Updated tag Handling for Image Pulls

* Added Throwing Exception with Invalid Build Images

* Fixed PyLint Issue

* Feat: added integration tests, container tag check and layer support  (#2780)

* Added integration tests

* Added click requirement on -u presence for container based options

* Added click class that checks -u presence for container based options

* Reformatting and fixing test cases

* Fix for integration test failures

* Added support for layers

* Resolve conflict

* Refactoring file location

* Addressing review comments, refactoring

* Reformatting logging sentence

* Refactoring container check for simplicity

* Clarifying test case

* Added unit tests on layer builds

* Improving readability

* Refactoring to simplify

* Removing unnecessary lines

* Refactoring integration test

* Shortening error message

* Adjusting test behaviour

* Added new unit tests

* Test refactoring

* Added new test class

* Removed unused import

* Improving help text

Co-authored-by: Qingchuan Ma <69653965+qingchm@users.noreply.github.com>
moelasmar pushed a commit to moelasmar/aws-sam-cli that referenced this pull request Jul 1, 2021
* feat(Public ECR): Changed Build Image Registry to Public ECR (aws#2708)

* Changed Build Image Registry to Public ECR

* Added  _IMAGE_URI_PREFIX

* Fixed Unit Tests

* Updated Image Format

* feat(Public ECR): Updated Test Image Name and Added Latest Tag Check (aws#2720)

* Added Tests for Latest Tag

* Added use_container Check for Verify Latest Tag Tests

* Fixed DotNet and Go Build Tests

* Added Missing use_container Check for test_build_function_and_layer

* feat(Public ECR): Changed Build Image Registry to Public ECR (aws#2708)

* Changed Build Image Registry to Public ECR

* Added  _IMAGE_URI_PREFIX

* Fixed Unit Tests

* Updated Image Format

* feat(Public ECR): Updated Test Image Name and Added Latest Tag Check (aws#2720)

* Added Tests for Latest Tag

* Added use_container Check for Verify Latest Tag Tests

* Fixed DotNet and Go Build Tests

* Added Missing use_container Check for test_build_function_and_layer

* feat(Public ECR): Added --build-image Options (aws#2725)

* Added Latest Tag Check

* Added --build-image Option

* Added Unit Tests

* Added Help Text and Fixed PyLint

* Fixed Path Validation for Unit Tests

* Updated Type Hintings

* Added _parse_key_value_pair

* Updated tag Handling for Image Pulls

* Added Throwing Exception with Invalid Build Images

* Fixed PyLint Issue

* Feat: added integration tests, container tag check and layer support  (aws#2780)

* Added integration tests

* Added click requirement on -u presence for container based options

* Added click class that checks -u presence for container based options

* Reformatting and fixing test cases

* Fix for integration test failures

* Added support for layers

* Resolve conflict

* Refactoring file location

* Addressing review comments, refactoring

* Reformatting logging sentence

* Refactoring container check for simplicity

* Clarifying test case

* Added unit tests on layer builds

* Improving readability

* Refactoring to simplify

* Removing unnecessary lines

* Refactoring integration test

* Shortening error message

* Adjusting test behaviour

* Added new unit tests

* Test refactoring

* Added new test class

* Removed unused import

* Improving help text

Co-authored-by: Qingchuan Ma <69653965+qingchm@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants