diff --git a/.clang-tidy b/.clang-tidy index d42139e..616d471 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,6 +4,7 @@ Checks: -modernize-use-trailing-return-type,-bugprone-easily-swappable-parameters,-readability-identifier-length' WarningsAsErrors: '*' HeaderFilterRegex: 'include/aws/.*\.h$' +ExcludeHeaderFilter: 'build/_deps/gtest-src.*' FormatStyle: 'none' CheckOptions: - key: modernize-pass-by-value.ValuesOnly diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 526b38a..7da0628 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -12,39 +12,24 @@ env: jobs: build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ubuntu-latest + strategy: + matrix: + arch: [ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.arch }} steps: - uses: actions/checkout@v3 - - name: Install Dependencies run: sudo apt-get update && sudo apt-get install -y clang-tidy libcurl4-openssl-dev - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy -DENABLE_TESTS=ON - name: Build It - # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - build-on-arm-too: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: uraimo/run-on-arch-action@v2 - with: - arch: aarch64 - distro: ubuntu20.04 - run: | - apt-get update && apt-get install -y cmake g++ clang-tidy libcurl4-openssl-dev - cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy - cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + - name: Test It + run: cd build && make && ctest build-demo: runs-on: ubuntu-latest @@ -68,15 +53,11 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/lambda-install make make aws-lambda-package-demo - - + format: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - name: Check Formatting run: ./ci/codebuild/format-check.sh - - diff --git a/README.md b/README.md index 74e60af..0f58b28 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,23 @@ $ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install $ make && make install ``` +### Running Unit Tests Locally + +To run the unit tests locally, follow these steps to build: + +```bash +$ cd aws-lambda-cpp +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTS=ON +$ make +``` + +Run unit tests: +```bash +$ ctest +``` + To consume this library in a project that is also using CMake, you would do: ```cmake @@ -68,7 +85,7 @@ static invocation_response my_handler(invocation_request const& req) "error type here" /*error_type*/); } - return invocation_response::success("json payload here" /*payload*/, + return invocation_response::success("{\"message:\":\"I fail if body length is bigger than 42!\"}" /*payload*/, "application/json" /*MIME type*/); } @@ -130,13 +147,19 @@ And finally, create the Lambda function: ``` $ aws lambda create-function --function-name demo \ --role \ ---runtime provided --timeout 15 --memory-size 128 \ +--runtime provided.al2023 --timeout 15 --memory-size 128 \ --handler demo --zip-file fileb://demo.zip ``` +> **N.B.** If you are building on `arm64`, you have to explicitly add the param `--architectures arm64`, so that you are setting up the proper architecture on AWS to run your supplied Lambda function. And to invoke the function: ```bash -$ aws lambda invoke --function-name demo --payload '{"answer":42}' output.txt +$ aws lambda invoke --function-name demo --cli-binary-format raw-in-base64-out --payload '{"answer":42}' output.txt +``` + +You can update your supplied function: +```bash +$ aws lambda update-function-code --function-name demo --zip-file fileb://demo.zip ``` ## Using the C++ SDK for AWS with this runtime @@ -150,7 +173,7 @@ Any *fully* compliant C++11 compiler targeting GNU/Linux x86-64 should work. Ple - Use Clang v3.3 or above ## Packaging, ABI, GNU C Library, Oh My! -Lambda runs your code on some version of Amazon Linux. It would be a less than ideal customer experience if you are forced to build your application on that platform and that platform only. +Lambda runs your code on some version of Amazon Linux. It would be a less than ideal customer experience if you are forced to build your application on that platform and that platform only. However, the freedom to build on any linux distro brings a challenge. The GNU C Library ABI. There is no guarantee the platform used to build the Lambda function has the same GLIBC version as the one used by AWS Lambda. In fact, you might not even be using GNU's implementation. For example you could build a C++ Lambda function using musl libc. @@ -196,10 +219,12 @@ curl_easy_setopt(curl_handle, CURLOPT_CAINFO, "/etc/pki/tls/certs/ca-bundle.crt" ```bash $ aws lambda create-function --function-name demo \ --role \ - --runtime provided --timeout 15 --memory-size 128 \ + --runtime provided.al2023 --timeout 15 --memory-size 128 \ --handler demo --code "S3Bucket=mys3bucket,S3Key=demo.zip" ``` +> **N.B.** See hint above if you are building on `arm64`. + 1. **My code is crashing, how can I debug it?** - Starting with [v0.2.0](https://github.com/awslabs/aws-lambda-cpp/releases/tag/v0.2.0) you should see a stack-trace of the crash site in the logs (which are typically stored in CloudWatch). diff --git a/examples/demo/main.cpp b/examples/demo/main.cpp index 358efe0..5381311 100644 --- a/examples/demo/main.cpp +++ b/examples/demo/main.cpp @@ -9,7 +9,7 @@ static invocation_response my_handler(invocation_request const& req) "error type here" /*error_type*/); } - return invocation_response::success("json payload here" /*payload*/, + return invocation_response::success("{\"message:\":\"I fail if body length is bigger than 42!\"}" /*payload*/, "application/json" /*MIME type*/); } diff --git a/packaging/packager b/packaging/packager index 0c4e749..c050696 100755 --- a/packaging/packager +++ b/packaging/packager @@ -171,20 +171,11 @@ exec \$LAMBDA_TASK_ROOT/lib/$PKG_LD --library-path \$LAMBDA_TASK_ROOT/lib \$LAMB EOF ) -bootstrap_script_no_libc=$(cat < "$PKG_DIR/bootstrap" else - echo -e "$bootstrap_script_no_libc" > "$PKG_DIR/bootstrap" + cp "$PKG_BIN_PATH" "$PKG_DIR/bootstrap" fi chmod +x "$PKG_DIR/bootstrap" # some shenanigans to create the right layout in the zip file without extraneous directories diff --git a/src/runtime.cpp b/src/runtime.cpp index 2ae91e2..1013a19 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -527,7 +527,7 @@ invocation_response invocation_response::failure(std::string const& error_messag r.m_success = false; r.m_content_type = "application/json"; r.m_payload = R"({"errorMessage":")" + json_escape(error_message) + R"(","errorType":")" + json_escape(error_type) + - R"(", "stackTrace":[]})"; + R"(","stackTrace":[]})"; return r; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 06bf89a..7406096 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,17 +1,50 @@ +cmake_minimum_required(VERSION 3.11) project(aws-lambda-runtime-tests LANGUAGES CXX) -find_package(AWSSDK COMPONENTS lambda iam) +if(DEFINED ENV{GITHUB_ACTIONS}) + # Fetch Google Test for unit tests + include(FetchContent) + FetchContent_Declare(gtest + URL https://github.com/google/googletest/archive/v1.12.0.tar.gz + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + # Configure build of googletest + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + set(INSTALL_GTEST OFF) + FetchContent_MakeAvailable(gtest) -add_executable(${PROJECT_NAME} - main.cpp - runtime_tests.cpp - version_tests.cpp - gtest/gtest-all.cc) + add_executable(unit_tests + unit/no_op_test.cpp) + target_link_libraries(unit_tests PRIVATE gtest_main aws-lambda-runtime) -target_link_libraries(${PROJECT_NAME} PRIVATE ${AWSSDK_LINK_LIBRARIES} aws-lambda-runtime) + # Register unit tests + include(GoogleTest) + gtest_discover_tests(unit_tests + PROPERTIES + LABELS "unit" + DISCOVERY_TIMEOUT 10) +else() + message(STATUS "Unit tests skipped: Not in GitHub Actions environment") +endif() -include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME} EXTRA_ARGS "--aws_prefix=${TEST_RESOURCE_PREFIX}") # requires CMake 3.10 or later -add_subdirectory(resources) +find_package(AWSSDK COMPONENTS lambda iam QUIET) + +if(AWSSDK_FOUND) + add_executable(${PROJECT_NAME} + integration/main.cpp + integration/runtime_tests.cpp + integration/version_tests.cpp + gtest/gtest-all.cc) + + target_link_libraries(${PROJECT_NAME} PRIVATE ${AWSSDK_LINK_LIBRARIES} aws-lambda-runtime) + + include(GoogleTest) + gtest_discover_tests(${PROJECT_NAME} EXTRA_ARGS "--aws_prefix=${TEST_RESOURCE_PREFIX}") + + add_subdirectory(resources) +else() + message(STATUS "Integration tests skipped: AWS SDK not found or not in GitHub Actions environment") +endif() diff --git a/tests/main.cpp b/tests/integration/main.cpp similarity index 97% rename from tests/main.cpp rename to tests/integration/main.cpp index d7700e8..2a112d3 100644 --- a/tests/main.cpp +++ b/tests/integration/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "gtest/gtest.h" +#include "../gtest/gtest.h" std::function()> get_console_logger_factory() { diff --git a/tests/runtime_tests.cpp b/tests/integration/runtime_tests.cpp similarity index 99% rename from tests/runtime_tests.cpp rename to tests/integration/runtime_tests.cpp index f118dab..843f815 100644 --- a/tests/runtime_tests.cpp +++ b/tests/integration/runtime_tests.cpp @@ -13,7 +13,7 @@ #include #include #include -#include "gtest/gtest.h" +#include "../gtest/gtest.h" #include #include #include diff --git a/tests/version_tests.cpp b/tests/integration/version_tests.cpp similarity index 93% rename from tests/version_tests.cpp rename to tests/integration/version_tests.cpp index 070b6dd..a0f546e 100644 --- a/tests/version_tests.cpp +++ b/tests/integration/version_tests.cpp @@ -1,5 +1,5 @@ #include -#include "gtest/gtest.h" +#include "../gtest/gtest.h" using namespace aws::lambda_runtime; diff --git a/tests/resources/lambda_function.cpp b/tests/resources/lambda_function.cpp index b0228d3..bf12fc0 100644 --- a/tests/resources/lambda_function.cpp +++ b/tests/resources/lambda_function.cpp @@ -41,12 +41,9 @@ int main(int argc, char* argv[]) handlers.emplace("binary_response", binary_response); handlers.emplace("crash_backtrace", crash_backtrace); - if (argc < 2) { - aws::logging::log_error("lambda_fun", "Missing handler argument. Exiting."); - return -1; - } - - auto it = handlers.find(argv[1]); + // Read the handler from the environment variable + const char* handler_name = std::getenv("_HANDLER"); + auto it = handlers.find(handler_name == nullptr ? "" : handler_name); if (it == handlers.end()) { aws::logging::log_error("lambda_fun", "Handler %s not found. Exiting.", argv[1]); return -2; diff --git a/tests/unit/no_op_test.cpp b/tests/unit/no_op_test.cpp new file mode 100644 index 0000000..c9a3b7d --- /dev/null +++ b/tests/unit/no_op_test.cpp @@ -0,0 +1,6 @@ +#include + +TEST(noop, dummy_test) +{ + ASSERT_EQ(0, 0); +}