Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
1552ea6
set up integ test folder structure
mingkun2020 Nov 9, 2020
a890b7c
rename test_integ to tests_integ to keep it consistent with the unite…
mingkun2020 Nov 9, 2020
5a16b78
Added test-integ action to Makefile to launch integration tests
mgrandis Nov 10, 2020
582262c
Added all the single templates, fixed an error in basic_function.yaml
mgrandis Nov 10, 2020
5117bf6
Added test_basic_function.py from the POC, untouched; removed the "ex…
mgrandis Nov 10, 2020
c070cdc
Added basic api inline openapi test as a standalone file to be merged…
mgrandis Nov 10, 2020
243edc3
Renamed basic api inline openapi test with the test prefix
mgrandis Nov 10, 2020
8a9efd2
fix: Reverted to range instead of enumerate
mgrandis Nov 10, 2020
b35d912
set up basic code structure for sam integration test
mingkun2020 Nov 11, 2020
eb72ddd
Merge branch 'foss-integ-test' of https://github.com/mingkun2020/serv…
mingkun2020 Nov 11, 2020
0f79f4b
Updated helpers and expected basic_function file for the new verifica…
mgrandis Nov 12, 2020
090560b
add expected json file and fix some bugs
mingkun2020 Nov 12, 2020
f7ee968
remove unnecessary print statement
mingkun2020 Nov 12, 2020
2bde985
update basic function test
mingkun2020 Nov 12, 2020
65c2ebd
Added missing expected files, updated runner
mgrandis Nov 13, 2020
b763e7e
Removed the ResourceStatus check for each resource
mgrandis Nov 13, 2020
5a11d35
Refactored the test to inherit a BaseTest class, moved the basic_appl…
mgrandis Nov 13, 2020
a1f583e
add upload zip file and resolve uri with url feature, need refactorin…
mingkun2020 Nov 13, 2020
d12abf4
add extra test class and update yaml load call
mingkun2020 Nov 13, 2020
d8eb9ba
fix bug, now allow create bucket in multi regions
mingkun2020 Nov 14, 2020
083942c
Added random suffix to stack and bucket names to avoid name collisions
mgrandis Nov 14, 2020
e68b81e
refactor: Moved some stuff into methods in base_test, updated the Mak…
mgrandis Nov 16, 2020
4ae155d
chore: Black formatting
mgrandis Nov 16, 2020
4130f3d
set up integ test folder structure
mingkun2020 Nov 9, 2020
923934b
rename test_integ to tests_integ to keep it consistent with the unite…
mingkun2020 Nov 9, 2020
67c2674
Added test-integ action to Makefile to launch integration tests
mgrandis Nov 10, 2020
ee5402e
Added all the single templates, fixed an error in basic_function.yaml
mgrandis Nov 10, 2020
0faf6e4
Added test_basic_function.py from the POC, untouched; removed the "ex…
mgrandis Nov 10, 2020
dedd33b
set up basic code structure for sam integration test
mingkun2020 Nov 11, 2020
b4eb18d
Added basic api inline openapi test as a standalone file to be merged…
mgrandis Nov 10, 2020
f7fc08b
Renamed basic api inline openapi test with the test prefix
mgrandis Nov 10, 2020
0a96214
fix: Reverted to range instead of enumerate
mgrandis Nov 10, 2020
a88b80b
Updated helpers and expected basic_function file for the new verifica…
mgrandis Nov 12, 2020
65bb94a
add expected json file and fix some bugs
mingkun2020 Nov 12, 2020
6a9fd89
remove unnecessary print statement
mingkun2020 Nov 12, 2020
dd9bec1
update basic function test
mingkun2020 Nov 12, 2020
8c900d2
Added missing expected files, updated runner
mgrandis Nov 13, 2020
a5bfc51
Removed the ResourceStatus check for each resource
mgrandis Nov 13, 2020
bb016f5
Refactored the test to inherit a BaseTest class, moved the basic_appl…
mgrandis Nov 13, 2020
6c85d6d
add upload zip file and resolve uri with url feature, need refactorin…
mingkun2020 Nov 13, 2020
896ef40
add extra test class and update yaml load call
mingkun2020 Nov 13, 2020
b45a890
fix bug, now allow create bucket in multi regions
mingkun2020 Nov 14, 2020
ae775b7
Added random suffix to stack and bucket names to avoid name collisions
mgrandis Nov 14, 2020
b61a189
refactor: Moved some stuff into methods in base_test, updated the Mak…
mgrandis Nov 16, 2020
ff18eee
chore: Black formatting
mgrandis Nov 16, 2020
e7bdf6c
refactor: No more references to BaseTest, using cls and self instead
mgrandis Nov 18, 2020
c3037ae
fix bug for creating bucket in us-east-1
mingkun2020 Nov 18, 2020
17f3572
Merge branch 'foss-integ-test' of https://github.com/mingkun2020/serv…
mingkun2020 Nov 18, 2020
da61baf
update _upload_resources and _clean_bucket after resolving merge conf…
mingkun2020 Nov 18, 2020
dab73e7
refactor: In BaseTest, moved some stuff to class attributes, added doc
mgrandis Nov 18, 2020
3e3f387
refactor: Moved the resource file maps to their own py file, file_res…
mgrandis Nov 18, 2020
cf1d3f2
feat: Fully implemented basic_api test and various refactors
mgrandis Nov 18, 2020
e41d19d
refactor: Renamed S3 dict from URI to URL
mgrandis Nov 18, 2020
d37fda5
feat: Implemented all the test_basic_api tests, added get_stack_stage…
mgrandis Nov 19, 2020
befee0e
feat: test_basic_application tests migrated
mgrandis Nov 19, 2020
0fac1bc
feat: Migrated test_basic_http_api
mgrandis Nov 19, 2020
1bdb60b
feat: Migrated basic_state_machine tests
mgrandis Nov 20, 2020
e1bf1f8
migrate all basic function test
mingkun2020 Nov 20, 2020
09e9789
migrate all basic layer version test
mingkun2020 Nov 20, 2020
ba3d54d
remove useless import and add NoRegionError in create bucket when reg…
mingkun2020 Nov 20, 2020
914cb79
Merge branch 'foss-integ-test' of https://github.com/mingkun2020/serv…
mingkun2020 Nov 20, 2020
9a3b154
refactor: Added url type to FILE_TO_S3_URL_MAP so we don't rely on th…
mgrandis Nov 20, 2020
30e841e
refactor: Removed unnecessary parameter
mgrandis Nov 20, 2020
4eaaed0
docs: Added docstrings for basic tests
mgrandis Nov 20, 2020
2e14e38
remove unused import and update layer version test with parameters
mingkun2020 Nov 20, 2020
7c91bb0
black reformating
mingkun2020 Nov 20, 2020
20e3cfd
update appveyor.yml file
mingkun2020 Nov 20, 2020
4091d3f
fix: Changed string formatting incompatible with Python 2 in base_tes…
mgrandis Nov 20, 2020
c79c53b
add make test-integ in appveyor.yml file
mingkun2020 Nov 20, 2020
b6e227b
Merge branch 'foss-integ-test' of https://github.com/mingkun2020/serv…
mingkun2020 Nov 20, 2020
a2918f6
add default region in appveyor environment
mingkun2020 Nov 20, 2020
ef7acfe
fix: Reverting adding aws-sam-cli to dev dependencies, it's for integ…
mgrandis Nov 21, 2020
1c4c1c8
refactor: Changed a remaining python3 string format syntax in _fill_t…
mgrandis Nov 21, 2020
2857627
refactor: Using URI throughout, yaml load/dump function, [] not used …
mgrandis Nov 23, 2020
e099b0b
feat: Added local Deployer class so that we don't depend on sam-cli a…
mgrandis Nov 24, 2020
6a97509
fix appveyor os.mkdir failure
mingkun2020 Nov 24, 2020
453a1ca
revert changes in appveyor.yml, run make test-integ in the tox.ini
mingkun2020 Nov 24, 2020
2cb57a9
black reformat
mingkun2020 Nov 24, 2020
abcc4aa
refactor: Get resource default returned values are most consistent, a…
mgrandis Nov 25, 2020
f97d759
refactor: Renamed get_stack_stages functions to include api, removed …
mgrandis Nov 26, 2020
656b457
docs: Added first version of README.md file to the tests_integ directory
mgrandis Nov 27, 2020
77d5b91
chagne hard code FILE_TO_S3_URI_MAP, CODE_KEY_TO_FILE_MAP to be param…
mingkun2020 Nov 27, 2020
1871bc8
fix: Need to tell pytest in the MakeFile to only search for *.py file…
mgrandis Nov 27, 2020
6b3ab9f
chore: Minor README.md wording/format changes
mgrandis Nov 27, 2020
f1e0569
change file_to_s3_uri_map and code_key_to_file to be class variables
mingkun2020 Nov 27, 2020
58e14c7
fix: black and black-check Make commands now only check *.py files in…
mgrandis Nov 28, 2020
724dfdf
docs: Moved the test_integ readme file to the root, renamed it, refer…
mgrandis Dec 2, 2020
c1c274b
Merge branch 'develop' into foss-integ-test
mgrandis Dec 18, 2020
40a7827
refactor: Using LogicalIdGenerator.HASH_LENGTH in verify_stack_resour…
mgrandis Dec 19, 2020
cc19689
fix: Fix table_print tests syntax errors for python2
mgrandis Dec 23, 2020
2fc0bb1
refactor: Removed the table_print tests which are complicated to port…
mgrandis Dec 23, 2020
463e1e6
refactor: Revert newline removal at the end of appveyor.yml
mgrandis Dec 23, 2020
886c692
for testing appveyor only, will revert later
mingkun2020 Jan 7, 2021
e04853f
Revert "for testing appveyor only, will revert later"
mingkun2020 Jan 7, 2021
bccea06
test appveyor failure, will revert later
mingkun2020 Jan 7, 2021
26eed84
Revert "test appveyor failure, will revert later"
mingkun2020 Jan 7, 2021
8711cfa
just for testing appveyor failure, will revert later
mingkun2020 Jan 8, 2021
35b5b09
for testing only, will revert later, add ssh access to appveyor vm
mingkun2020 Jan 8, 2021
8237cbf
for testing only, will revert later
mingkun2020 Jan 8, 2021
9de384e
Revert "for testing only, will revert later"
mingkun2020 Jan 8, 2021
3aaa34c
revert all the previous test changes
mingkun2020 Jan 8, 2021
12bc745
create a appveyor config file for integration test
mingkun2020 Jan 11, 2021
8f33c82
move integration test to a separate appveyor yml file
mingkun2020 Jan 11, 2021
6806e78
delete extra blank line
mingkun2020 Jan 12, 2021
3d380b8
increase retry times to bypass Throttling
mingkun2020 Jan 12, 2021
d88bddd
black reformatting
mingkun2020 Jan 12, 2021
0bddb9a
refactor helper.py to have meaningful class name
mingkun2020 Jan 15, 2021
4848990
rename folder name and make command name to have the sam style with s…
mingkun2020 Jan 15, 2021
d070397
create a client provider class with lazy initialization
mingkun2020 Jan 15, 2021
b3930c1
add comments for clients
mingkun2020 Jan 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ init:
pip install -e '.[dev]'

test:
pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests
pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests/*

test-integ:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can we match the naming with SAM CLI? https://github.com/aws/aws-sam-cli/blob/develop/Makefile#L17 That way we are not trying to remember which project does which thing?

Copy link
Contributor Author

@mingkun2020 mingkun2020 Jan 15, 2021

Choose a reason for hiding this comment

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

Updated the command name to integ-test.

pytest --no-cov tests_integ/*
Copy link
Contributor

Choose a reason for hiding this comment

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

Same nit here: I would prefer if we standardize our naming and structure across all our repos we own.

Copy link
Contributor

@mgrandis mgrandis Jan 13, 2021

Choose a reason for hiding this comment

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

Just so I understand correctly, does this mean moving the whole content of the current tests dir under tests/unit and moving the integration tests under tests/integration?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just moving the tests_integration content in tests/integration will make the unit tests also run the integration tests so we may have to move everything.

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 have rename the integration tests folder to have the same name with samcli. Since the unit tests are directly under tests and we don't have a unit folder for them, so currently we keep the integration folder outside the tests folder to separate them from the unit tests. If we create a unit folder for the unit tests in the future, then we can directly move the integration into tests to make them consistently with samcli style.


black:
black setup.py samtranslator/* tests/* bin/*
black setup.py samtranslator/* tests/* tests_integ/* bin/*

black-check:
black --check setup.py samtranslator/* tests/* bin/*
black --check setup.py samtranslator/* tests/* tests_integ/* bin/*

# Command to run everytime you make changes to verify everything works
dev: test
Expand All @@ -27,6 +30,7 @@ Usage: $ make [TARGETS]
TARGETS
init Initialize and install the requirements and dev-requirements for this project.
test Run the Unit tests.
test-integ Run the Integration tests.
dev Run all development tests after a change.
pr Perform all checks before submitting a Pull Request.

Expand Down
Empty file added tests_integ/__init__.py
Empty file.
Empty file added tests_integ/helpers/__init__.py
Empty file.
108 changes: 108 additions & 0 deletions tests_integ/helpers/base_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
from pathlib import Path
from unittest.case import TestCase

import boto3
import pytest
import yaml
from samcli.lib.deploy.deployer import Deployer
from tests_integ.helpers.helpers import transform_template, verify_stack_resources, generate_suffix, create_bucket

STACK_NAME_PREFIX = "sam-integ-stack-"
S3_BUCKET_PREFIX = "sam-integ-bucket-"
CODE_KEY_TO_FILE_MAP = {"codeuri": "code.zip", "contenturi": "layer1.zip", "definitionuri": "swagger1.json"}


class BaseTest(TestCase):
@classmethod
def setUpClass(cls):
BaseTest.tests_integ_dir = Path(__file__).resolve().parents[1]
BaseTest.resources_dir = Path(BaseTest.tests_integ_dir, "resources")
BaseTest.template_dir = Path(BaseTest.resources_dir, "templates", "single")
BaseTest.output_dir = BaseTest.tests_integ_dir
BaseTest.expected_dir = Path(BaseTest.resources_dir, "expected", "single")
code_dir = Path(BaseTest.resources_dir, "code")

BaseTest.s3_bucket_name = S3_BUCKET_PREFIX + generate_suffix()
session = boto3.session.Session()
my_region = session.region_name
create_bucket(BaseTest.s3_bucket_name, region=my_region)

s3_client = boto3.client("s3")
BaseTest.code_key_to_url = {}

for key, file_name in CODE_KEY_TO_FILE_MAP.items():
code_path = str(Path(code_dir, file_name))
s3_client.upload_file(code_path, BaseTest.s3_bucket_name, file_name)
code_url = f"s3://{BaseTest.s3_bucket_name}/{file_name}"
BaseTest.code_key_to_url[key] = code_url

@classmethod
def tearDownClass(cls) -> None:
BaseTest._clean_bucket()

@classmethod
def _clean_bucket(cls):
s3_client = boto3.client("s3")
response = s3_client.list_objects(Bucket=BaseTest.s3_bucket_name)
for content in response["Contents"]:
s3_client.delete_object(Key=content["Key"], Bucket=BaseTest.s3_bucket_name)
s3_client.delete_bucket(Bucket=BaseTest.s3_bucket_name)

def setUp(self):
self.cloudformation_client = boto3.client("cloudformation")
self.deployer = Deployer(self.cloudformation_client, changeset_prefix="sam-integ-")

def create_and_verify_stack(self, file_name):
input_file_path = str(Path(BaseTest.template_dir, file_name + ".yaml"))
self.output_file_path = str(Path(BaseTest.output_dir, "cfn_" + file_name + ".yaml"))
expected_resource_path = str(Path(BaseTest.expected_dir, file_name + ".json"))
self.stack_name = STACK_NAME_PREFIX + file_name.replace("_", "-") + generate_suffix()

self.sub_input_file_path = self._update_template(input_file_path, file_name)
transform_template(self.sub_input_file_path, self.output_file_path)
self._deploy_stack()
self._verify_stack(expected_resource_path)

def tearDown(self):
self.cloudformation_client.delete_stack(StackName=self.stack_name)
if os.path.exists(self.output_file_path):
os.remove(self.output_file_path)
if os.path.exists(self.sub_input_file_path):
os.remove(self.sub_input_file_path)

def _update_template(self, input_file_path, file_name):
updated_template_path = str(Path(BaseTest.output_dir, "sub_" + file_name + ".yaml"))
with open(input_file_path, "r") as f:
data = f.read()
for key, s3_url in BaseTest.code_key_to_url.items():
data = data.replace(f"${{{key}}}", s3_url)
yaml_doc = yaml.load(data, Loader=yaml.FullLoader)

with open(updated_template_path, "w") as f:
yaml.dump(yaml_doc, f)

return updated_template_path

def _deploy_stack(self):
with open(self.output_file_path, "r") as cfn_file:
result, changeset_type = self.deployer.create_and_wait_for_changeset(
stack_name=self.stack_name,
cfn_template=cfn_file.read(),
parameter_values=[],
capabilities=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"],
role_arn=None,
notification_arns=[],
s3_uploader=None,
tags=[],
)
self.deployer.execute_changeset(result["Id"], self.stack_name)
self.deployer.wait_for_execute(self.stack_name, changeset_type)

def _verify_stack(self, expected_resource_path):
stacks_description = self.cloudformation_client.describe_stacks(StackName=self.stack_name)
stack_resources = self.cloudformation_client.list_stack_resources(StackName=self.stack_name)
# verify if the stack was successfully created
self.assertEqual(stacks_description["Stacks"][0]["StackStatus"], "CREATE_COMPLETE")
# verify if the stack contains the expected resources
self.assertTrue(verify_stack_resources(expected_resource_path, stack_resources))
89 changes: 89 additions & 0 deletions tests_integ/helpers/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import json
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 a better name then "helpers"? We should try to be explicit (single principle) and in my experience, these types of classes/files just become dumping grounds

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, also helpers/helpers.py is redundant.
We could maybe move the transform_template function to helpers\template.py and the rest to helpers\resource.py.

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

import logging
import re
import random
import string # not deprecated, a bug from pylint https://www.logilab.org/ticket/2481
from functools import reduce

import boto3
from botocore.exceptions import ClientError

from samtranslator.model.exceptions import InvalidDocumentException
from samtranslator.translator.managed_policy_translator import ManagedPolicyLoader
from samtranslator.translator.transform import transform
from samtranslator.yaml_helper import yaml_parse

RANDOM_SUFFIX_LENGTH = 12


def transform_template(input_file_path, output_file_path):
LOG = logging.getLogger(__name__)
iam_client = boto3.client("iam")

with open(input_file_path, "r") as f:
sam_template = yaml_parse(f)

try:
cloud_formation_template = transform(sam_template, {}, ManagedPolicyLoader(iam_client))
cloud_formation_template_prettified = json.dumps(cloud_formation_template, indent=2)

with open(output_file_path, "w") as f:
f.write(cloud_formation_template_prettified)

print("Wrote transformed CloudFormation template to: " + output_file_path)
except InvalidDocumentException as e:
errorMessage = reduce(lambda message, error: message + " " + error.message, e.causes, e.message)
LOG.error(errorMessage)
errors = map(lambda cause: cause.message, e.causes)
LOG.error(errors)


def verify_stack_resources(expected_file_path, stack_resources):
with open(expected_file_path, "r") as expected_data:
expected_resources = _sort_resources(json.load(expected_data))
parsed_resources = _sort_resources(stack_resources["StackResourceSummaries"])

if len(expected_resources) != len(parsed_resources):
return False

for i in range(len(expected_resources)):
exp = expected_resources[i]
parsed = parsed_resources[i]
if not re.fullmatch(exp["LogicalResourceId"] + "([0-9a-f]{10})?", parsed["LogicalResourceId"]):
return False
if exp["ResourceType"] != parsed["ResourceType"]:
return False
return True


def generate_suffix():
# Very basic random letters generator
return "".join(random.choice(string.ascii_lowercase) for i in range(RANDOM_SUFFIX_LENGTH))


def _sort_resources(resources):
return sorted(resources, key=lambda d: d["LogicalResourceId"])


def create_bucket(bucket_name, region=None):
"""Create an S3 bucket in a specified region
copy code from boto3 doc example
MG: removed the try so that the exception bubbles up and interrupts the test
If a region is not specified, the bucket is created in the S3 default
region (us-east-1).
:param bucket_name: Bucket to create
:param region: String region to create bucket in, e.g., 'us-west-2'
:return: True if bucket created, else False
"""

# Create bucket
if region is None:
s3_client = boto3.client("s3")
s3_client.create_bucket(Bucket=bucket_name)
else:
s3_client = boto3.client("s3", region_name=region)
location = {"LocationConstraint": region}
s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration=location)
Binary file added tests_integ/resources/code/code.zip
Binary file not shown.
Binary file added tests_integ/resources/code/layer1.zip
Binary file not shown.
Loading