diff --git a/.env b/.env new file mode 100644 index 000000000..00993d713 --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ +# Test configuration for RIE and dockerized tests +# Customize these values as needed for testing both local and on github + +# Handlers to build +HANDLERS_TO_BUILD=basic-lambda + +HANDLER=basic-lambda + +# Output directory for built binaries +OUTPUT_DIR=test/dockerized/tasks + +# Max concurrent Lambda invocations for LMI mode +RIE_MAX_CONCURRENCY=4 diff --git a/.github/workflows/dockerized-test.yml b/.github/workflows/dockerized-test.yml new file mode 100644 index 000000000..01c2df9e3 --- /dev/null +++ b/.github/workflows/dockerized-test.yml @@ -0,0 +1,47 @@ +name: dockerized-test + +permissions: + contents: read + +on: + push: + branches: [ main ] + pull_request: + branches: [ '*' ] + workflow_dispatch: + + +jobs: + dockerized-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Load environment variables + run: | + if [ -f .env ]; then + set -a + source .env + set +a + echo "HANDLERS_TO_BUILD=${HANDLERS_TO_BUILD}" >> $GITHUB_ENV + echo "OUTPUT_DIR=${OUTPUT_DIR}" >> $GITHUB_ENV + fi + + - name: Build Lambda artifacts for testing + run: | + mkdir -p test/dockerized/tasks + OUTPUT_DIR="$(pwd)/test/dockerized/tasks" make build-examples + ls -la test/dockerized/tasks/ + + - name: Build base test image with RIE and custom entrypoint + run: | + docker build . -t local/test-base -f Dockerfile.test + + - name: Run tests + uses: aws/containerized-test-runner-for-aws-lambda@main + with: + suiteFileArray: '["./test/dockerized/suites/*.json"]' + dockerImageName: 'local/test-base' + taskFolder: './test/dockerized/tasks' \ No newline at end of file diff --git a/.gitignore b/.gitignore index d5e188a43..0652944cd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target lambda-runtime/libtest.rmeta lambda-integration-tests/target Cargo.lock +.test-runner # Built AWS Lambda zipfile lambda.zip @@ -16,3 +17,7 @@ build node_modules cdk.out + +# Test artifacts +Dockerfile.test-with-tasks +test/dockerized/tasks/ diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000..b36b1f28c --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,10 @@ +FROM public.ecr.aws/lambda/provided:al2023 + +ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie +RUN chmod +x /usr/local/bin/aws-lambda-rie + +COPY scripts/custom-lambda-entrypoint.sh /usr/local/bin/lambda-entrypoint +RUN chmod +x /usr/local/bin/lambda-entrypoint + +ENTRYPOINT ["/usr/local/bin/lambda-entrypoint"] +CMD ["basic-lambda"] diff --git a/Makefile b/Makefile index d27d0e47f..d4ffa4ba4 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,17 @@ INTEG_EXTENSIONS := extension-fn extension-trait logs-trait # Using musl to run extensions on both AL1 and AL2 INTEG_ARCH := x86_64-unknown-linux-musl RIE_MAX_CONCURRENCY ?= 4 +OUTPUT_DIR ?= test/dockerized/tasks +HANDLERS_TO_BUILD ?= +HANDLER ?= + +# Load environment variables from .env file if it exists +-include .env +export + +.PHONY: help pr-check integration-tests check-event-features fmt build-examples test-rie test-rie-lmi nuke test-dockerized + +.DEFAULT_GOAL := help define uppercase $(shell sed -r 's/(^|-)(\w)/\U\2/g' <<< $(1)) @@ -112,9 +123,68 @@ check-event-features: fmt: cargo +nightly fmt --all +build-examples: + HANDLERS_TO_BUILD=${HANDLERS_TO_BUILD} OUTPUT_DIR=${OUTPUT_DIR} ./scripts/build-examples.sh + +nuke: + docker kill $$(docker ps -q) + +test-dockerized: build-examples + @echo "Running dockerized tests locally..." + + @echo "Building base Docker image with RIE and custom entrypoint..." + docker build \ + -t local/test-base \ + -f Dockerfile.test \ + . + + @echo "Setting up containerized test runner..." + @if [ ! -d ".test-runner" ]; then \ + echo "Cloning containerized-test-runner-for-aws-lambda..."; \ + git clone --quiet https://github.com/aws/containerized-test-runner-for-aws-lambda.git .test-runner; \ + fi + @echo "Building test runner Docker image..." + @docker build -t test-runner:local -f .test-runner/Dockerfile .test-runner + + @echo "Running tests in Docker..." + @docker run --rm \ + -e INPUT_SUITE_FILE_ARRAY='["./test/dockerized/suites/*.json"]' \ + -e DOCKER_IMAGE_NAME=local/test-base \ + -e TASK_FOLDER=./test/dockerized/tasks \ + -e GITHUB_WORKSPACE=/workspace \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$(CURDIR):/workspace" \ + -w /workspace \ + test-runner:local + test-rie: - ./scripts/test-rie.sh $(EXAMPLE) + HANDLER="$(HANDLER)" ./scripts/test-rie.sh # Run RIE in Lambda Managed Instance (LMI) mode with concurrent polling. test-rie-lmi: - RIE_MAX_CONCURRENCY=$(RIE_MAX_CONCURRENCY) ./scripts/test-rie.sh $(EXAMPLE) + RIE_MAX_CONCURRENCY=$(RIE_MAX_CONCURRENCY) HANDLER="$(HANDLER)" ./scripts/test-rie.sh $(EXAMPLE) + +help: ## Show this help message + @echo 'Usage: make [target]' + @echo '' + @echo 'Available targets:' + @echo ' pr-check Run pre-commit checks (fmt, clippy, tests)' + @echo ' integration-tests Build and run AWS integration tests' + @echo ' check-event-features Test individual event features' + @echo ' fmt Format code with cargo fmt' + @echo ' build-examples Build example Lambda functions' + @echo ' Usage: EXAMPLES="basic-lambda" OUTPUT_DIR=/make build-examples' + @echo ' test-rie Test Lambda with Runtime Interface Emulator' + @echo ' Usage: HANDLERS_TO_BUILD="basic-lambda basic-sqs" make test-rie' + @echo ' Usage: HANDLERS_TO_BUILD="basic-lambda" HANDLER="basic-lambda" make test-rie' + @echo ' test-rie-lmi Test RIE in Lambda Managed Instance mode' + @echo ' Usage: RIE_MAX_CONCURRENCY=4 HANDLERS_TO_BUILD="basic-lambda-concurrent" make test-rie-lmi' + @echo ' test-dockerized Run dockerized test harness' + @echo ' nuke Kill all running Docker containers' + @echo '' + @echo 'Environment variables:' + @echo ' EXAMPLES Space-separated list of examples to build (for build-examples)' + @echo ' HANDLERS_TO_BUILD Space-separated list of handlers to build for RIE (for test-rie)' + @echo ' HANDLER Specific handler to run (defaults to first in HANDLERS_TO_BUILD)' + @echo ' OUTPUT_DIR Directory for built binaries (default: /tmp/var-task for build-examples, /var/task for Docker)' + @echo ' RIE_MAX_CONCURRENCY Max concurrent Lambda invocations for LMI mode (for test-rie-lmi)' diff --git a/README.md b/README.md index 6ff25c9b9..6b7addf8a 100644 --- a/README.md +++ b/README.md @@ -392,34 +392,60 @@ You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/c ### Local testing with Runtime Interface Emulator (RIE) -For testing with the official AWS Lambda Runtime Interface Emulator, use the provided RIE testing infrastructure: +For testing with the official AWS Lambda Runtime Interface Emulator: ```bash make test-rie ``` -By default, this uses the `basic-lambda` example. To test a different example: +By default, this builds and tests the `basic-lambda` example. To build and test a custom handler: ```bash -make test-rie EXAMPLE=basic-sqs -make test-rie EXAMPLE=http-basic-lambda +HANDLER="basic-tenant-id" make test-rie ``` -To test Lambda Managed Instances (concurrent polling), use: +To test Lambda Managed Instances (concurrent polling): ```bash -make test-rie-lmi EXAMPLE=basic-lambda-concurrent +RIE_MAX_CONCURRENCY=4 make test-rie ``` -This command will: -1. Build a Docker image with Rust toolchain and RIE -2. Compile the specified example inside the Linux container -3. Start the RIE container on port 9000 -4. Display the appropriate curl command for testing - Different examples expect different payload formats. Check the example's source code in `examples/EXAMPLE_NAME/src/main.rs` -This provides automated testing with Docker and RIE, ensuring your Lambda functions work in a Linux environment identical to AWS Lambda. +### Dockerized test harness + +For automated testing with AWS's containerized test runner: + +```bash +make test-dockerized +``` + +This runs test suites defined in `test/dockerized/*.json` files using the [containerized-test-runner-for-aws-lambda](https://github.com/aws/containerized-test-runner-for-aws-lambda). Test suites specify handlers to test (from examples), request payloads, and expected response assertions. + +Example test suite (`test/dockerized/core.json`): +```json +{ + "tests": [ + { + "name": "test_echo", + "handler": "basic-lambda", + "request": { + "command": "test" + }, + "assertions": [ + { + "response": { + "msg": "Command test executed." + }, + "transform": "{msg: .msg}" + } + ] + } + ] +} +``` + +The `transform` field uses jq syntax to extract specific fields from responses before comparison, useful when responses include dynamic fields like request IDs. ### Lambda Debug Proxy diff --git a/scripts/build-examples.sh b/scripts/build-examples.sh new file mode 100755 index 000000000..b479059dc --- /dev/null +++ b/scripts/build-examples.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +OUTPUT_DIR="${OUTPUT_DIR:-/tmp/var-task}" +HANDLERS_TO_BUILD="${HANDLERS_TO_BUILD:-}" + +mkdir -p "$OUTPUT_DIR" + +echo "Building handlers: ${HANDLERS_TO_BUILD}" + + +for handler in ${HANDLERS_TO_BUILD}; do + dir="examples/$handler" + [ ! -f "$dir/Cargo.toml" ] && echo "✗ $handler not found" && continue + + echo "Building $handler..." + (cd "$dir" && cargo build --release) || continue + + [ -f "$dir/target/release/$handler" ] && cp "$dir/target/release/$handler" "$OUTPUT_DIR/" && echo "✓ $handler" +done + +echo "" +ls -lh "$OUTPUT_DIR/" 2>/dev/null || echo "No binaries built" diff --git a/scripts/custom-lambda-entrypoint.sh b/scripts/custom-lambda-entrypoint.sh new file mode 100644 index 000000000..fb02b26c5 --- /dev/null +++ b/scripts/custom-lambda-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# custom entrypoint script to allow selection of multiple rust binaries for dockerized tests +set -e + +HANDLER=${1:-basic-lambda} + +if [ -f "/var/task/$HANDLER" ]; then + ln -sf "/var/task/$HANDLER" "${LAMBDA_RUNTIME_DIR}/bootstrap" + exec /usr/local/bin/aws-lambda-rie "${LAMBDA_RUNTIME_DIR}/bootstrap" +else + echo "Error: Handler '$HANDLER' not found in /var/task" + echo "Available handlers:" + ls -la /var/task + exit 1 +fi diff --git a/scripts/test-rie.sh b/scripts/test-rie.sh index 3561a8ee5..c5949fe8f 100755 --- a/scripts/test-rie.sh +++ b/scripts/test-rie.sh @@ -26,4 +26,4 @@ fi echo "" echo "Press Ctrl+C to stop the container." -wait $CONTAINER_PID +wait $CONTAINER_PID \ No newline at end of file diff --git a/test/dockerized/suites/core.json b/test/dockerized/suites/core.json new file mode 100644 index 000000000..5a21d0665 --- /dev/null +++ b/test/dockerized/suites/core.json @@ -0,0 +1,19 @@ +{ + "tests": [ + { + "name": "test_echo", + "handler": "basic-lambda", + "request": { + "command": "test" + }, + "assertions": [ + { + "response": { + "msg": "Command test executed." + }, + "transform": "{msg: .msg}" + } + ] + } + ] +}