|
16 | 16 | - [Drafting release notes](#drafting-release-notes)
|
17 | 17 | - [Run end to end tests](#run-end-to-end-tests)
|
18 | 18 | - [Structure](#structure)
|
19 |
| - - [Workflow](#workflow) |
| 19 | + - [Mechanics](#mechanics) |
| 20 | + - [Authoring an E2E test](#authoring-an-e2e-test) |
| 21 | + - [Internals](#internals) |
| 22 | + - [Parallelization](#parallelization) |
| 23 | + - [CDK safe parallelization](#cdk-safe-parallelization) |
20 | 24 | - [Releasing a documentation hotfix](#releasing-a-documentation-hotfix)
|
21 | 25 | - [Maintain Overall Health of the Repo](#maintain-overall-health-of-the-repo)
|
22 | 26 | - [Manage Roadmap](#manage-roadmap)
|
@@ -266,7 +270,169 @@ You probably notice we have multiple `conftest.py`, `infrastructure.py`, and `ha
|
266 | 270 | - Feature-level `e2e/<feature>/conftest` deploys stacks in parallel and make them independent of each other.
|
267 | 271 | - **`handlers/`**. Lambda function handlers that will be automatically deployed and exported as PascalCase for later use.
|
268 | 272 |
|
269 |
| -#### Parallelization |
| 273 | +#### Mechanics |
| 274 | + |
| 275 | +Under `BaseInfrastructure`, we hide the complexity of handling CDK parallel deployments, exposing CloudFormation Outputs, building Lambda Layer with the latest available code, and creating Lambda functions found in `handlers`. |
| 276 | + |
| 277 | +This allows us to benefit from test and deployment parallelization, use IDE step-through debugging for a single test, run a subset of tests and only deploy their related infrastructure, without any custom configuration. |
| 278 | + |
| 279 | +> Class diagram to understand abstraction built when defining a new stack (`LoggerStack`) |
| 280 | +
|
| 281 | +```mermaid |
| 282 | +classDiagram |
| 283 | + class InfrastructureProvider { |
| 284 | + <<interface>> |
| 285 | + +deploy() Dict |
| 286 | + +delete() |
| 287 | + +create_resources() |
| 288 | + +create_lambda_functions(function_props: Dict) |
| 289 | + } |
| 290 | +
|
| 291 | + class BaseInfrastructure { |
| 292 | + +deploy() Dict |
| 293 | + +delete() |
| 294 | + +create_lambda_functions(function_props: Dict) Dict~Functions~ |
| 295 | + +add_cfn_output(name: str, value: str, arn: Optional[str]) |
| 296 | + } |
| 297 | +
|
| 298 | + class TracerStack { |
| 299 | + +create_resources() |
| 300 | + } |
| 301 | +
|
| 302 | + class LoggerStack { |
| 303 | + +create_resources() |
| 304 | + } |
| 305 | +
|
| 306 | + class MetricsStack { |
| 307 | + +create_resources() |
| 308 | + } |
| 309 | +
|
| 310 | + class EventHandlerStack { |
| 311 | + +create_resources() |
| 312 | + } |
| 313 | +
|
| 314 | + InfrastructureProvider <|-- BaseInfrastructure : implement |
| 315 | + BaseInfrastructure <|-- TracerStack : inherit |
| 316 | + BaseInfrastructure <|-- LoggerStack : inherit |
| 317 | + BaseInfrastructure <|-- MetricsStack : inherit |
| 318 | + BaseInfrastructure <|-- EventHandlerStack : inherit |
| 319 | +``` |
| 320 | + |
| 321 | +#### Authoring an E2E test |
| 322 | + |
| 323 | +Imagine you're going to create E2E for Event Handler feature for the first time. |
| 324 | + |
| 325 | +As a mental model, you'll need to: **(1)** Define infrastructure, **(2)** Deploy/Delete infrastructure when tests run, and **(3)** Expose resources for E2E tests. |
| 326 | + |
| 327 | +**Define infrastructure** |
| 328 | + |
| 329 | +We use CDK as our Infrastructure as Code tool of choice. Before you start using CDK, you need to take the following steps: |
| 330 | + |
| 331 | +1. Create `tests/e2e/event_handler/infrastructure.py` file |
| 332 | +2. Create a new class `EventHandlerStack` and inherit from `BaseInfrastructure` |
| 333 | +3. Override `create_resources` method and define your infrastructure using CDK |
| 334 | +4. (Optional) Create a Lambda function under `handlers/alb_handler.py` |
| 335 | + |
| 336 | +> Excerpt `infrastructure.py` for Event Handler |
| 337 | +
|
| 338 | +```python |
| 339 | +class EventHandlerStack(BaseInfrastructure): |
| 340 | + def create_resources(self): |
| 341 | + functions = self.create_lambda_functions() |
| 342 | + |
| 343 | + self._create_alb(function=functions["AlbHandler"]) |
| 344 | + ... |
| 345 | + |
| 346 | + def _create_alb(self, function: Function): |
| 347 | + vpc = ec2.Vpc.from_lookup( |
| 348 | + self.stack, |
| 349 | + "VPC", |
| 350 | + is_default=True, |
| 351 | + region=self.region, |
| 352 | + ) |
| 353 | + |
| 354 | + alb = elbv2.ApplicationLoadBalancer(self.stack, "ALB", vpc=vpc, internet_facing=True) |
| 355 | + CfnOutput(self.stack, "ALBDnsName", value=alb.load_balancer_dns_name) |
| 356 | + ... |
| 357 | +``` |
| 358 | + |
| 359 | +> Excerpt `alb_handler.py` for Event Handler |
| 360 | +
|
| 361 | +```python |
| 362 | +from aws_lambda_powertools.event_handler import ALBResolver, Response, content_types |
| 363 | + |
| 364 | +app = ALBResolver() |
| 365 | + |
| 366 | + |
| 367 | +@app.get("/todos") |
| 368 | +def hello(): |
| 369 | + return Response( |
| 370 | + status_code=200, |
| 371 | + content_type=content_types.TEXT_PLAIN, |
| 372 | + body="Hello world", |
| 373 | + cookies=["CookieMonster", "MonsterCookie"], |
| 374 | + headers={"Foo": ["bar", "zbr"]}, |
| 375 | + ) |
| 376 | + |
| 377 | + |
| 378 | +def lambda_handler(event, context): |
| 379 | + return app.resolve(event, context) |
| 380 | +``` |
| 381 | + |
| 382 | +**Deploy/Delete infrastructure when tests run** |
| 383 | + |
| 384 | +We need to instruct Pytest to deploy our infrastructure when our tests start, and delete it when they complete (successfully or not). |
| 385 | + |
| 386 | +For this, we create a `test/e2e/event_handler/conftest.py` and create fixture scoped to our test module. This will remain static and will not need any further modification in the future. |
| 387 | + |
| 388 | +> Excerpt `conftest.py` for Event Handler |
| 389 | +
|
| 390 | +```python |
| 391 | +import pytest |
| 392 | + |
| 393 | +from tests.e2e.event_handler.infrastructure import EventHandlerStack |
| 394 | + |
| 395 | + |
| 396 | +@pytest.fixture(autouse=True, scope="module") |
| 397 | +def infrastructure(): |
| 398 | + """Setup and teardown logic for E2E test infrastructure |
| 399 | +
|
| 400 | + Yields |
| 401 | + ------ |
| 402 | + Dict[str, str] |
| 403 | + CloudFormation Outputs from deployed infrastructure |
| 404 | + """ |
| 405 | + stack = EventHandlerStack() |
| 406 | + try: |
| 407 | + yield stack.deploy() |
| 408 | + finally: |
| 409 | + stack.delete() |
| 410 | + |
| 411 | +``` |
| 412 | + |
| 413 | +**Expose resources for E2E tests** |
| 414 | + |
| 415 | +Within our tests, we should now have access to the `infrastructure` fixture we defined. We can access any Stack Output using pytest dependency injection. |
| 416 | + |
| 417 | +> Excerpt `test_header_serializer.py` for Event Handler |
| 418 | +
|
| 419 | +```python |
| 420 | +@pytest.fixture |
| 421 | +def alb_basic_listener_endpoint(infrastructure: dict) -> str: |
| 422 | + dns_name = infrastructure.get("ALBDnsName") |
| 423 | + port = infrastructure.get("ALBBasicListenerPort", "") |
| 424 | + return f"http://{dns_name}:{port}" |
| 425 | + |
| 426 | + |
| 427 | +def test_alb_headers_serializer(alb_basic_listener_endpoint): |
| 428 | + # GIVEN |
| 429 | + url = f"{alb_basic_listener_endpoint}/todos" |
| 430 | + ... |
| 431 | +``` |
| 432 | + |
| 433 | +#### Internals |
| 434 | + |
| 435 | +##### Parallelization |
270 | 436 |
|
271 | 437 | We parallelize our end-to-end tests to benefit from speed and isolate Lambda functions to ease assessing side effects (e.g., traces, logs, etc.). The following diagram demonstrates the process we take every time you use `make e2e`:
|
272 | 438 |
|
@@ -299,6 +465,10 @@ graph TD
|
299 | 465 | ResultCollection --> DeployEnd["Delete Stacks"]
|
300 | 466 | ```
|
301 | 467 |
|
| 468 | +##### CDK safe parallelization |
| 469 | + |
| 470 | +Describe CDK App, Stack, synth, etc. |
| 471 | + |
302 | 472 | ### Releasing a documentation hotfix
|
303 | 473 |
|
304 | 474 | You can rebuild the latest documentation without a full release via this [GitHub Actions Workflow](https://github.com/awslabs/aws-lambda-powertools-python/actions/workflows/rebuild_latest_docs.yml). Choose `Run workflow`, keep `develop` as the branch, and input the latest Powertools version available.
|
|
0 commit comments